chore(LithoFilterPatch): add compatibility with older versions of AGP

This commit is contained in:
inotia00 2024-09-14 22:33:35 +09:00
parent cc71acf44e
commit 435154cae3
3 changed files with 145 additions and 130 deletions

View File

@ -5,84 +5,112 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction 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.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod 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.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH 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.EmptyComponentsFingerprint
import app.revanced.patches.shared.litho.fingerprints.LithoFilterPatchConstructorFingerprint
import app.revanced.patches.shared.litho.fingerprints.PathBuilderFingerprint 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.getStringInstructionIndex
import app.revanced.util.getTargetIndexOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.getTargetIndexReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow
import app.revanced.util.resultOrThrow 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.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.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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.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 import java.io.Closeable
@Suppress("SpellCheckingInspection", "unused") @Suppress("SpellCheckingInspection", "unused")
object LithoFilterPatch : BytecodePatch( object LithoFilterPatch : BytecodePatch(
setOf( setOf(
ByteBufferFingerprint,
EmptyComponentsFingerprint, EmptyComponentsFingerprint,
LithoFilterPatchConstructorFingerprint,
SetByteBufferFingerprint
) )
), Closeable { ), Closeable {
private const val INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR = private const val INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/LithoFilterPatch;" "$COMPONENTS_PATH/LithoFilterPatch;"
private const val INTEGRATIONS_FILER_CLASS_DESCRIPTOR = private const val INTEGRATIONS_FILER_ARRAY_DESCRIPTOR =
"$COMPONENTS_PATH/Filter;" "[$COMPONENTS_PATH/Filter;"
private lateinit var filterArrayMethod: MutableMethod
private var filterCount = 0
internal lateinit var addFilter: (String) -> Unit internal lateinit var addFilter: (String) -> Unit
private set 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) { override fun execute(context: BytecodeContext) {
SetByteBufferFingerprint.resultOrThrow().let { // region Pass the buffer into Integrations.
it.mutableMethod.apply {
val insertIndex = getTargetIndexOrThrow(Opcode.IF_EQZ) + 1
addInstruction( ByteBufferFingerprint.resultOrThrow().mutableMethod.addInstruction(
insertIndex, 0,
"invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V" "invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V"
) )
}
}
EmptyComponentsFingerprint.resultOrThrow().let { // endregion
it.mutableMethod.apply {
// resolves fingerprint. var (emptyComponentMethod, emptyComponentLabel) =
EmptyComponentsFingerprint.resultOrThrow().let {
PathBuilderFingerprint.resolve(context, it.classDef) PathBuilderFingerprint.resolve(context, it.classDef)
emptyComponentMethod = this with(it.mutableMethod) {
emptyComponentMethodName = name val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1
val emptyComponentMethodReference =
getInstruction<ReferenceInstruction>(emptyComponentMethodIndex).reference
val emptyComponentFieldReference =
getInstruction<ReferenceInstruction>(emptyComponentMethodIndex + 2).reference
val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 val label = """
val emptyComponentMethodReference = move-object/from16 v0, p1
getInstruction<ReferenceInstruction>(emptyComponentMethodIndex).reference invoke-static {v0}, $emptyComponentMethodReference
val emptyComponentFieldReference = move-result-object v0
getInstruction<ReferenceInstruction>(emptyComponentMethodIndex + 2).reference 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<OneRegisterInstruction>(it + 1).registerA
val insertIndex = it + 2
addInstructionsWithLabels(
insertIndex, """
if-nez v$insertRegister, :ignore
""" + emptyComponentLabel,
ExternalLabel("ignore", getInstruction(insertIndex))
)
}
emptyComponentLabel = """ emptyComponentLabel = """
move-object/from16 v0, p1 const/4 v0, 0x0
invoke-static {v0}, $emptyComponentMethodReference
move-result-object v0
iget-object v0, v0, $emptyComponentFieldReference
return-object v0 return-object v0
""" """
} }
@ -90,65 +118,27 @@ object LithoFilterPatch : BytecodePatch(
PathBuilderFingerprint.resultOrThrow().let { PathBuilderFingerprint.resultOrThrow().let {
it.mutableMethod.apply { it.mutableMethod.apply {
// If the EmptyComponents Method and the PathBuilder Method are different, checkMethodSignatureMatch(this)
// 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 { val stringBuilderIndex = indexOfFirstInstructionOrThrow {
// If the return value of the PathBuilder Method is null, opcode == Opcode.IPUT_OBJECT &&
// it means that pathBuilder has been filtered by the LithoFilterPatch. getReference<FieldReference>()?.type == "Ljava/lang/StringBuilder;"
// (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<OneRegisterInstruction>(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 =
getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/lang/StringBuilder;")
val stringBuilderRegister = val stringBuilderRegister =
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
val emptyStringIndex = getStringInstructionIndex("") val emptyStringIndex = getStringInstructionIndex("")
val identifierRegister = getInstruction<TwoRegisterInstruction>(
val identifierIndex = indexOfFirstInstructionReversedOrThrow(emptyStringIndex) {
getTargetIndexReversedOrThrow(emptyStringIndex, Opcode.IPUT_OBJECT) opcode == Opcode.IPUT_OBJECT
val identifierRegister = && getReference<FieldReference>()?.type == "Ljava/lang/String;"
getInstruction<TwoRegisterInstruction>(identifierIndex).registerA }
).registerA
val objectIndex = getTargetIndexOrThrow(emptyStringIndex, Opcode.INVOKE_VIRTUAL) val objectRegister = getInstruction<FiveRegisterInstruction>(
val objectRegister = getInstruction<BuilderInstruction35c>(objectIndex).registerC indexOfFirstInstructionOrThrow(emptyStringIndex) {
opcode == Opcode.INVOKE_VIRTUAL
}
).registerC
val insertIndex = stringBuilderIndex + 1 val insertIndex = stringBuilderIndex + 1
@ -163,30 +153,69 @@ object LithoFilterPatch : BytecodePatch(
} }
} }
LithoFilterPatchConstructorFingerprint.resultOrThrow().let { // Create a new method to get the filter array to avoid register conflicts.
it.mutableMethod.apply { // This fixes an issue with Integrations compiled with Android Gradle Plugin 8.3.0+.
removeInstructions(0, 6) // 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 -> lithoFilterMethods
addInstructions( .first { it.name == "<clinit>" }
0, """ .apply {
new-instance v0, $classDescriptor val setArrayIndex = indexOfFirstInstructionOrThrow {
invoke-direct {v0}, $classDescriptor-><init>()V opcode == Opcode.SPUT_OBJECT &&
const/16 v3, ${filterCount++} getReference<FieldReference>()?.type == INTEGRATIONS_FILER_ARRAY_DESCRIPTOR
aput-object v0, v2, v3 }
""" val setArrayRegister =
getInstruction<OneRegisterInstruction>(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-><init>()V
const/16 v1, ${filterCount++}
aput-object v0, v2, v1
"""
)
} }
} }
override fun close() = LithoFilterPatchConstructorFingerprint.result!! override fun close() = filterArrayMethod.addInstructions(
.mutableMethod.addInstructions( 0,
0, """ """
const/16 v1, $filterCount const/16 v0, $filterCount
new-array v2, v1, [$INTEGRATIONS_FILER_CLASS_DESCRIPTOR new-array v2, v0, $INTEGRATIONS_FILER_ARRAY_DESCRIPTOR
const/4 v1, 0x1 """
""" )
)
} }

View File

@ -5,7 +5,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint
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
internal object SetByteBufferFingerprint : MethodFingerprint( internal object ByteBufferFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("I", "Ljava/nio/ByteBuffer;"), parameters = listOf("I", "Ljava/nio/ByteBuffer;"),

View File

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