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 67d4c54a6..545556945 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt @@ -5,84 +5,112 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions +import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH +import app.revanced.patches.shared.litho.fingerprints.ByteBufferFingerprint 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 +import app.revanced.util.getReference import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +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.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.util.MethodUtil import java.io.Closeable @Suppress("SpellCheckingInspection", "unused") object LithoFilterPatch : BytecodePatch( setOf( + ByteBufferFingerprint, EmptyComponentsFingerprint, - LithoFilterPatchConstructorFingerprint, - SetByteBufferFingerprint ) ), Closeable { private const val INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/LithoFilterPatch;" - private const val INTEGRATIONS_FILER_CLASS_DESCRIPTOR = - "$COMPONENTS_PATH/Filter;" + private const val INTEGRATIONS_FILER_ARRAY_DESCRIPTOR = + "[$COMPONENTS_PATH/Filter;" + + private lateinit var filterArrayMethod: MutableMethod + private var filterCount = 0 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) { - SetByteBufferFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IF_EQZ) + 1 + // region Pass the buffer into Integrations. - addInstruction( - insertIndex, - "invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V" - ) - } - } + ByteBufferFingerprint.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V" + ) - EmptyComponentsFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - // resolves fingerprint. + // endregion + + var (emptyComponentMethod, emptyComponentLabel) = + EmptyComponentsFingerprint.resultOrThrow().let { PathBuilderFingerprint.resolve(context, it.classDef) - emptyComponentMethod = this - emptyComponentMethodName = name + with(it.mutableMethod) { + val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 + val emptyComponentMethodReference = + getInstruction(emptyComponentMethodIndex).reference + val emptyComponentFieldReference = + getInstruction(emptyComponentMethodIndex + 2).reference - val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 - val emptyComponentMethodReference = - getInstruction(emptyComponentMethodIndex).reference - val emptyComponentFieldReference = - getInstruction(emptyComponentMethodIndex + 2).reference + val label = """ + move-object/from16 v0, p1 + invoke-static {v0}, $emptyComponentMethodReference + move-result-object v0 + iget-object v0, v0, $emptyComponentFieldReference + return-object v0 + """ + + Pair(this, label) + } + } + + fun checkMethodSignatureMatch(pathBuilder: MutableMethod) = emptyComponentMethod.apply { + if (!MethodUtil.methodSignaturesMatch(pathBuilder, this)) { + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + MethodUtil.methodSignaturesMatch(pathBuilder, reference) + } + .map { (index, _) -> index } + .reversed() + .forEach { + val insertRegister = + getInstruction(it + 1).registerA + val insertIndex = it + 2 + + addInstructionsWithLabels( + insertIndex, """ + if-nez v$insertRegister, :ignore + """ + emptyComponentLabel, + ExternalLabel("ignore", getInstruction(insertIndex)) + ) + } emptyComponentLabel = """ - move-object/from16 v0, p1 - invoke-static {v0}, $emptyComponentMethodReference - move-result-object v0 - iget-object v0, v0, $emptyComponentFieldReference + const/4 v0, 0x0 return-object v0 """ } @@ -90,65 +118,27 @@ object LithoFilterPatch : BytecodePatch( 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" + checkMethodSignatureMatch(this) - 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 = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Ljava/lang/StringBuilder;" } - - val stringBuilderIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/lang/StringBuilder;") val stringBuilderRegister = getInstruction(stringBuilderIndex).registerA val emptyStringIndex = getStringInstructionIndex("") - - val identifierIndex = - getTargetIndexReversedOrThrow(emptyStringIndex, Opcode.IPUT_OBJECT) - val identifierRegister = - getInstruction(identifierIndex).registerA - - val objectIndex = getTargetIndexOrThrow(emptyStringIndex, Opcode.INVOKE_VIRTUAL) - val objectRegister = getInstruction(objectIndex).registerC + val identifierRegister = getInstruction( + indexOfFirstInstructionReversedOrThrow(emptyStringIndex) { + opcode == Opcode.IPUT_OBJECT + && getReference()?.type == "Ljava/lang/String;" + } + ).registerA + val objectRegister = getInstruction( + indexOfFirstInstructionOrThrow(emptyStringIndex) { + opcode == Opcode.INVOKE_VIRTUAL + } + ).registerC val insertIndex = stringBuilderIndex + 1 @@ -163,30 +153,69 @@ object LithoFilterPatch : BytecodePatch( } } - LithoFilterPatchConstructorFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - removeInstructions(0, 6) + // Create a new method to get the filter array to avoid register conflicts. + // This fixes an issue with Integrations compiled with Android Gradle Plugin 8.3.0+. + // https://github.com/ReVanced/revanced-patches/issues/2818 + val lithoFilterMethods = context.findClass(INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR) + ?.mutableClass + ?.methods + ?: throw PatchException("LithoFilterPatch class not found.") - addFilter = { classDescriptor -> - addInstructions( - 0, """ - new-instance v0, $classDescriptor - invoke-direct {v0}, $classDescriptor->()V - const/16 v3, ${filterCount++} - aput-object v0, v2, v3 - """ + lithoFilterMethods + .first { it.name == "" } + .apply { + val setArrayIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.SPUT_OBJECT && + getReference()?.type == INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + } + val setArrayRegister = + getInstruction(setArrayIndex).registerA + val addedMethodName = "getFilterArray" + + addInstructions( + setArrayIndex, """ + invoke-static {}, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->$addedMethodName()$INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + move-result-object v$setArrayRegister + """ + ) + + filterArrayMethod = ImmutableMethod( + definingClass, + addedMethodName, + emptyList(), + INTEGRATIONS_FILER_ARRAY_DESCRIPTOR, + AccessFlags.PRIVATE or AccessFlags.STATIC, + null, + null, + MutableMethodImplementation(3), + ).toMutable().apply { + addInstruction( + 0, + "return-object v2" ) } + + lithoFilterMethods.add(filterArrayMethod) } + + addFilter = { classDescriptor -> + filterArrayMethod.addInstructions( + 0, + """ + new-instance v0, $classDescriptor + invoke-direct {v0}, $classDescriptor->()V + const/16 v1, ${filterCount++} + aput-object v0, v2, v1 + """ + ) } } - override fun close() = LithoFilterPatchConstructorFingerprint.result!! - .mutableMethod.addInstructions( - 0, """ - const/16 v1, $filterCount - new-array v2, v1, [$INTEGRATIONS_FILER_CLASS_DESCRIPTOR - const/4 v1, 0x1 - """ - ) + override fun close() = filterArrayMethod.addInstructions( + 0, + """ + const/16 v0, $filterCount + new-array v2, v0, $INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + """ + ) } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt similarity index 94% rename from src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt index bfa2fc004..8a14c41fd 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt @@ -5,7 +5,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -internal object SetByteBufferFingerprint : MethodFingerprint( +internal object ByteBufferFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("I", "Ljava/nio/ByteBuffer;"), diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt deleted file mode 100644 index a307628be..000000000 --- a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.patches.shared.litho.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH -import com.android.tools.smali.dexlib2.AccessFlags - -internal object LithoFilterPatchConstructorFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR, - customFingerprint = { methodDef, _ -> - methodDef.definingClass == "$COMPONENTS_PATH/LithoFilterPatch;" - } -) \ No newline at end of file