From f15a5d52597b21c0400c7f72309f747c0c378989 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 26 May 2024 16:01:24 +0900 Subject: [PATCH] feat(YouTube Music): add support versions `7.01.53` ~ `7.02.51` --- .../MinimizedPlaybackPatch.kt | 3 +- .../MusicBrowserServiceFingerprint.kt | 9 +- .../music/utils/compatibility/Constants.kt | 5 +- .../utils/settings/SettingsBytecodePatch.kt | 2 +- .../utils/settings/SettingsBytecodePatch.kt | 2 +- .../fingerprints/SharedSettingFingerprint.kt | 2 +- .../patches/shared/litho/LithoFilterPatch.kt | 83 +++++++++++++++++-- .../EmptyComponentsFingerprint.kt | 18 ++++ .../fingerprints/PathBuilderFingerprint.kt | 15 +--- .../kotlin/app/revanced/util/BytecodeUtils.kt | 10 ++- 10 files changed, 119 insertions(+), 30 deletions(-) rename src/main/kotlin/app/revanced/patches/shared/{settings => }/fingerprints/SharedSettingFingerprint.kt (87%) create mode 100644 src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/EmptyComponentsFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/MinimizedPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/MinimizedPlaybackPatch.kt index 09b00fe40..d1eac7de9 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/MinimizedPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/MinimizedPlaybackPatch.kt @@ -10,6 +10,7 @@ import app.revanced.patches.music.misc.minimizedplayback.fingerprints.MinimizedP import app.revanced.patches.music.misc.minimizedplayback.fingerprints.MusicBrowserServiceFingerprint import app.revanced.patches.music.misc.minimizedplayback.fingerprints.PodCastConfigFingerprint import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.util.getStartsWithStringInstructionIndex import app.revanced.util.getStringInstructionIndex import app.revanced.util.getWalkerMethod import app.revanced.util.patch.BaseBytecodePatch @@ -51,7 +52,7 @@ object MinimizedPlaybackPatch : BaseBytecodePatch( MusicBrowserServiceFingerprint.resultOrThrow().let { it.mutableMethod.apply { val targetIndex = - getStringInstructionIndex("MBS: Return empty root for client: %s, isFullMediaBrowserEnabled: %b, is client browsable: %b, isRedAccount: %b") + getStartsWithStringInstructionIndex("MBS: Return empty root for client: %s") for (index in targetIndex downTo 0) { if (getInstruction(index).opcode != Opcode.INVOKE_VIRTUAL) continue diff --git a/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/fingerprints/MusicBrowserServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/fingerprints/MusicBrowserServiceFingerprint.kt index 8e890d912..b1db408ff 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/fingerprints/MusicBrowserServiceFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/minimizedplayback/fingerprints/MusicBrowserServiceFingerprint.kt @@ -2,14 +2,17 @@ package app.revanced.patches.music.misc.minimizedplayback.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getStartsWithStringInstructionIndex import com.android.tools.smali.dexlib2.AccessFlags internal object MusicBrowserServiceFingerprint : MethodFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("Ljava/lang/String;", "Landroid/os/Bundle;"), - strings = listOf("MBS: Return empty root for client: %s, isFullMediaBrowserEnabled: %b, is client browsable: %b, isRedAccount: %b"), - customFingerprint = { methodDef, _ -> - methodDef.definingClass.endsWith("/MusicBrowserService;") + customFingerprint = custom@{ methodDef, _ -> + if (!methodDef.definingClass.endsWith("/MusicBrowserService;")) + return@custom false + + methodDef.getStartsWithStringInstructionIndex("MBS: Return empty root for client: %s") > 0 } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt b/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt index 9bafd7e81..169ff6d17 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt @@ -10,8 +10,9 @@ object Constants { "6.29.58", "6.31.55", "6.33.52", - "6.50.53", - "6.51.53" + "6.51.53", + "7.01.53", + "7.02.51", ) ) ) diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt index 143ccac8c..12a2441ce 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt @@ -16,7 +16,7 @@ import app.revanced.patches.music.utils.settings.fingerprints.GoogleApiActivityF import app.revanced.patches.music.utils.settings.fingerprints.PreferenceFingerprint import app.revanced.patches.music.utils.settings.fingerprints.SettingsHeadersFragmentFingerprint import app.revanced.patches.shared.integrations.Constants.INTEGRATIONS_UTILS_CLASS_DESCRIPTOR -import app.revanced.patches.shared.settings.fingerprints.SharedSettingFingerprint +import app.revanced.patches.shared.fingerprints.SharedSettingFingerprint import app.revanced.util.getTargetIndex import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode diff --git a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt index a0541bf9c..1b834e30a 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt @@ -14,7 +14,7 @@ import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.LabelA import app.revanced.patches.reddit.utils.settings.fingerprints.AcknowledgementsLabelBuilderFingerprint import app.revanced.patches.reddit.utils.settings.fingerprints.OssLicensesMenuActivityOnCreateFingerprint import app.revanced.patches.reddit.utils.settings.fingerprints.SettingsStatusLoadFingerprint -import app.revanced.patches.shared.settings.fingerprints.SharedSettingFingerprint +import app.revanced.patches.shared.fingerprints.SharedSettingFingerprint import app.revanced.util.getTargetIndex import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.resultOrThrow diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/fingerprints/SharedSettingFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SharedSettingFingerprint.kt similarity index 87% rename from src/main/kotlin/app/revanced/patches/shared/settings/fingerprints/SharedSettingFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/fingerprints/SharedSettingFingerprint.kt index 2b3f05e66..059291260 100644 --- a/src/main/kotlin/app/revanced/patches/shared/settings/fingerprints/SharedSettingFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SharedSettingFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.shared.settings.fingerprints +package app.revanced.patches.shared.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.shared.integrations.Constants.INTEGRATIONS_SETTING_CLASS_DESCRIPTOR diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt b/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt index 5e08b10cd..5b6f4b1d1 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt @@ -7,8 +7,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH +import app.revanced.patches.shared.litho.fingerprints.EmptyComponentsFingerprint import app.revanced.patches.shared.litho.fingerprints.LithoFilterPatchConstructorFingerprint import app.revanced.patches.shared.litho.fingerprints.PathBuilderFingerprint import app.revanced.patches.shared.litho.fingerprints.SetByteBufferFingerprint @@ -19,6 +21,7 @@ import app.revanced.util.getTargetIndexWithFieldReferenceType import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import java.io.Closeable @@ -26,8 +29,8 @@ import java.io.Closeable @Suppress("SpellCheckingInspection", "unused") object LithoFilterPatch : BytecodePatch( setOf( + EmptyComponentsFingerprint, LithoFilterPatchConstructorFingerprint, - PathBuilderFingerprint, SetByteBufferFingerprint ) ), Closeable { @@ -40,6 +43,13 @@ object LithoFilterPatch : BytecodePatch( internal lateinit var addFilter: (String) -> Unit private set + private lateinit var emptyComponentMethod: MutableMethod + + private lateinit var emptyComponentLabel: String + private lateinit var emptyComponentMethodName: String + + private lateinit var pathBuilderMethodCall: String + private var filterCount = 0 override fun execute(context: BytecodeContext) { @@ -55,14 +65,75 @@ object LithoFilterPatch : BytecodePatch( } } - PathBuilderFingerprint.resultOrThrow().let { + EmptyComponentsFingerprint.resultOrThrow().let { it.mutableMethod.apply { + // resolves fingerprint. + PathBuilderFingerprint.resolve(context, it.classDef) + emptyComponentMethod = this + emptyComponentMethodName = name + val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 val emptyComponentMethodReference = getInstruction(emptyComponentMethodIndex).reference val emptyComponentFieldReference = getInstruction(emptyComponentMethodIndex + 2).reference + emptyComponentLabel = """ + move-object/from16 v0, p1 + invoke-static {v0}, $emptyComponentMethodReference + move-result-object v0 + iget-object v0, v0, $emptyComponentFieldReference + return-object v0 + """ + } + } + + PathBuilderFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + // If the EmptyComponents Method and the PathBuilder Method are different, + // new inject way is required. + // TODO: Refactor LithoFilter patch when support for YouTube 18.29.38 ~ 19.17.41 and YT Music 6.29.58 ~ 6.51.53 is dropped. + if (emptyComponentMethodName != name) { + // In this case, the access modifier of the method that handles PathBuilder is 'AccessFlags.PRIVATE or AccessFlags.FINAL. + // Methods that handle PathBuilder are invoked by methods that handle EmptyComponents. + // 'pathBuilderMethodCall' is a reference that invokes the PathBuilder Method. + pathBuilderMethodCall = "$definingClass->$name(" + for (i in 0 until parameters.size) { + pathBuilderMethodCall += parameterTypes[i] + } + pathBuilderMethodCall += ")$returnType" + + emptyComponentMethod.apply { + // If the return value of the PathBuilder Method is null, + // it means that pathBuilder has been filtered by the LithoFilterPatch. + // (Refer comments below.) + // Returns emptyComponents. + for (index in implementation!!.instructions.size - 1 downTo 0) { + val instruction = getInstruction(index) + if ((instruction as? ReferenceInstruction)?.reference.toString() != pathBuilderMethodCall) + continue + + val insertRegister = getInstruction(index + 1).registerA + val insertIndex = index + 2 + + addInstructionsWithLabels( + insertIndex, """ + if-nez v$insertRegister, :ignore + """ + emptyComponentLabel, + ExternalLabel("ignore", getInstruction(insertIndex)) + ) + } + } + + // If the EmptyComponents Method and the PathBuilder Method are different, + // PathBuilder Method's returnType cannot cast emptyComponents. + // So just returns null value. + emptyComponentLabel = """ + const/4 v0, 0x0 + return-object v0 + """ + } + val stringBuilderIndex = getTargetIndexWithFieldReferenceType("Ljava/lang/StringBuilder;") val stringBuilderRegister = getInstruction(stringBuilderIndex).registerA @@ -81,12 +152,8 @@ object LithoFilterPatch : BytecodePatch( invoke-static {v$stringBuilderRegister, v$identifierRegister, v$objectRegister}, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->filter(Ljava/lang/StringBuilder;Ljava/lang/String;Ljava/lang/Object;)Z move-result v$stringBuilderRegister if-eqz v$stringBuilderRegister, :filter - move-object/from16 v0, p1 - invoke-static {v0}, $emptyComponentMethodReference - move-result-object v0 - iget-object v0, v0, $emptyComponentFieldReference - return-object v0 - """, ExternalLabel("filter", getInstruction(insertIndex)) + """ + emptyComponentLabel, + ExternalLabel("filter", getInstruction(insertIndex)) ) } } diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/EmptyComponentsFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/EmptyComponentsFingerprint.kt new file mode 100644 index 000000000..b3fd01a1b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/EmptyComponentsFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.shared.litho.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object EmptyComponentsFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.INVOKE_STATIC_RANGE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT + ), + strings = listOf("Error while converting %s") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/PathBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/PathBuilderFingerprint.kt index 4eb2e44db..9489c3fc3 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/PathBuilderFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/PathBuilderFingerprint.kt @@ -1,18 +1,11 @@ package app.revanced.patches.shared.litho.fingerprints -import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode +/** + * Since YouTube v19.18.41 and YT Music 7.01.53, pathBuilder is being handled by a different Method. + */ internal object PathBuilderFingerprint : MethodFingerprint( returnType = "L", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.INVOKE_STATIC_RANGE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT - ), - strings = listOf("Error while converting %s") + strings = listOf("Number of bits must be positive") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 0bb78155a..bd02b8cd0 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -18,7 +18,6 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMut import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -212,7 +211,14 @@ fun Method.getEmptyStringInstructionIndex() fun Method.getStringInstructionIndex(value: String) = implementation?.let { it.instructions.indexOfFirst { instruction -> instruction.opcode == Opcode.CONST_STRING - && (instruction as? BuilderInstruction21c)?.reference.toString() == value + && (instruction as? ReferenceInstruction)?.reference.toString() == value + } +} ?: -1 + +fun Method.getStartsWithStringInstructionIndex(value: String) = implementation?.let { + it.instructions.indexOfFirst { instruction -> + instruction.opcode == Opcode.CONST_STRING + && (instruction as? ReferenceInstruction)?.reference.toString().startsWith(value) } } ?: -1