refactor(YouTube/Return YouTube Dislike): make patch more robust by removing opcode patterns from fingerprints

This commit is contained in:
inotia00 2024-01-17 19:38:10 +09:00
parent 34e5606c22
commit dd2f636a4a
6 changed files with 74 additions and 176 deletions

View File

@ -3,8 +3,6 @@ package app.revanced.patches.youtube.utils.returnyoutubedislike.general
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
@ -16,11 +14,8 @@ import app.revanced.patches.youtube.utils.playerresponse.PlayerResponsePatch
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.DislikeFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.LikeFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.RemoveLikeFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.TextComponentAtomicReferenceFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.TextComponentAtomicReferenceLegacyFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.TextComponentConstructorFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.TextComponentContextFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.general.fingerprints.TextComponentTmpFingerprint
import app.revanced.patches.youtube.utils.returnyoutubedislike.oldlayout.ReturnYouTubeDislikeOldLayoutPatch
import app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.ReturnYouTubeDislikeRollingNumberPatch
import app.revanced.patches.youtube.utils.returnyoutubedislike.shorts.ReturnYouTubeDislikeShortsPatch
@ -28,10 +23,8 @@ import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.utils.videoid.general.VideoIdPatch
import app.revanced.util.exception
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
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.Reference
@Patch(
name = "Return YouTube Dislike",
@ -101,88 +94,34 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
TextComponentConstructorFingerprint.result?.let { parentResult ->
// Resolves fingerprints
val parentClassDef = parentResult.classDef
TextComponentContextFingerprint.resolve(context, parentClassDef)
TextComponentTmpFingerprint.resolve(context, parentClassDef)
TextComponentAtomicReferenceFingerprint.resolve(context, parentClassDef)
TextComponentAtomicReferenceLegacyFingerprint.resolve(context, parentClassDef)
TextComponentContextFingerprint.resolve(context, parentResult.classDef)
TextComponentContextFingerprint.result?.let {
it.mutableMethod.apply {
val booleanIndex = it.scanResult.patternScanResult!!.endIndex
val conversionContextFieldIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IGET_OBJECT
&& (instruction as ReferenceInstruction).reference.toString().endsWith("Ljava/util/Map;")
} - 1
val conversionContextFieldReference =
getInstruction<ReferenceInstruction>(conversionContextFieldIndex).reference
for (index in booleanIndex downTo 0) {
if (getInstruction(index).opcode != Opcode.IGET_OBJECT) continue
val targetReference =
getInstruction<ReferenceInstruction>(index).reference.toString()
if (targetReference.endsWith("Ljava/util/Map;")) {
conversionContextFieldReference =
getInstruction<ReferenceInstruction>(index - 1).reference
break
}
}
}
} ?: throw TextComponentContextFingerprint.exception
TextComponentTmpFingerprint.result?.let {
it.mutableMethod.apply {
val startIndex = it.scanResult.patternScanResult!!.startIndex
tmpRegister =
getInstruction<FiveRegisterInstruction>(startIndex).registerE
}
} ?: throw TextComponentTmpFingerprint.exception
val textComponentAtomicReferenceResult =
TextComponentAtomicReferenceFingerprint.result
?: TextComponentAtomicReferenceLegacyFingerprint.result
?: throw TextComponentAtomicReferenceLegacyFingerprint.exception
TextComponentAtomicReferenceFingerprint.result?.let {
it.mutableMethod.apply {
val startIndex = it.scanResult.patternScanResult!!.startIndex
val originalRegisterA =
getInstruction<TwoRegisterInstruction>(startIndex + 2).registerA
replaceInstruction(
startIndex + 2,
"move-object v$originalRegisterA, v$tmpRegister"
)
replaceInstruction(
startIndex + 1,
"move-result-object v$tmpRegister"
)
}
}
textComponentAtomicReferenceResult.let {
it.mutableMethod.apply {
val atomicReferenceStartIndex = it.scanResult.patternScanResult!!.startIndex
val insertIndex = it.scanResult.patternScanResult!!.endIndex
val moveCharSequenceInstruction =
getInstruction<TwoRegisterInstruction>(insertIndex)
val atomicReferenceRegister =
getInstruction<FiveRegisterInstruction>(atomicReferenceStartIndex).registerC
val charSequenceRegister =
moveCharSequenceInstruction.registerB
val charSequenceIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IGET_OBJECT
&& (instruction as ReferenceInstruction).reference.toString().endsWith("Ljava/util/BitSet;")
} - 1
val charSequenceRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerA
val freeRegister = getInstruction<TwoRegisterInstruction>(charSequenceIndex).registerB
addInstructions(
insertIndex + 1, """
move-object/from16 v$tmpRegister, p0
iget-object v$tmpRegister, v$tmpRegister, $conversionContextFieldReference
invoke-static {v$tmpRegister, v$atomicReferenceRegister, v$charSequenceRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
charSequenceIndex - 1, """
move-object/from16 v$freeRegister, p0
iget-object v$freeRegister, v$freeRegister, $conversionContextFieldReference
invoke-static {v$freeRegister, v$charSequenceRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$charSequenceRegister
move-object v${moveCharSequenceInstruction.registerA}, v${charSequenceRegister}
"""
)
removeInstruction(insertIndex)
}
}
} ?: throw TextComponentContextFingerprint.exception
} ?: throw TextComponentConstructorFingerprint.exception
VideoIdPatch.injectCall("$INTEGRATIONS_RYD_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V")
@ -208,9 +147,6 @@ object ReturnYouTubeDislikePatch : BytecodePatch(
private const val FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/ReturnYouTubeDislikeFilterPatch;"
private lateinit var conversionContextFieldReference: Reference
private var tmpRegister: Int = 12
private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind)
private data class VotePatch(val fingerprint: MethodFingerprint, val voteKind: Vote)

View File

@ -1,19 +0,0 @@
package app.revanced.patches.youtube.utils.returnyoutubedislike.general.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
object TextComponentAtomicReferenceFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.MOVE_OBJECT,
Opcode.CHECK_CAST,
Opcode.MOVE_OBJECT
)
)

View File

@ -1,18 +0,0 @@
package app.revanced.patches.youtube.utils.returnyoutubedislike.general.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
object TextComponentAtomicReferenceLegacyFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.MOVE_OBJECT
)
)

View File

@ -1,16 +0,0 @@
package app.revanced.patches.youtube.utils.returnyoutubedislike.general.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
object TextComponentTmpFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT, // last instruction of the method
)
)

View File

@ -2,11 +2,13 @@ package app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber
import app.revanced.patcher.data.BytecodeContext
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.replaceInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.utils.fingerprints.RollingNumberTextViewAnimationUpdateFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.fingerprints.RollingNumberMeasureAnimatedTextFingerprint
@ -31,6 +33,7 @@ object ReturnYouTubeDislikeRollingNumberPatch : BytecodePatch(
RollingNumberSetterFingerprint,
RollingNumberMeasureTextParentFingerprint,
RollingNumberTextViewFingerprint,
RollingNumberMeasureAnimatedTextFingerprint,
RollingNumberTextViewAnimationUpdateFingerprint
)
) {
@ -88,41 +91,42 @@ object ReturnYouTubeDislikeRollingNumberPatch : BytecodePatch(
}
} ?: throw RollingNumberSetterFingerprint.exception
RollingNumberMeasureTextParentFingerprint.result?.let { parentResult ->
// Rolling Number text views use the measured width of the raw string for layout.
// Modify the measure text calculation to include the left drawable separator if needed.
RollingNumberMeasureAnimatedTextFingerprint.also {
it.resolve(
context,
parentResult.classDef
)
}.result?.let {
RollingNumberMeasureAnimatedTextFingerprint.result?.let {
it.mutableMethod.apply {
val returnInstructionIndex = it.scanResult.patternScanResult!!.endIndex
val endIndex = it.scanResult.patternScanResult!!.endIndex
val measuredTextWidthIndex = endIndex - 2
val measuredTextWidthRegister =
getInstruction<OneRegisterInstruction>(returnInstructionIndex).registerA
getInstruction<TwoRegisterInstruction>(measuredTextWidthIndex).registerA
replaceInstruction( // Replace instruction to preserve control flow label.
returnInstructionIndex,
"invoke-static {p1, v$measuredTextWidthRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F"
)
addInstructions(
returnInstructionIndex + 1, """
endIndex + 1, """
invoke-static {p1, v$measuredTextWidthRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F
move-result v$measuredTextWidthRegister
return v$measuredTextWidthRegister
"""
)
val ifGeIndex = implementation!!.instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IF_GE
}
val ifGeInstruction = getInstruction<TwoRegisterInstruction>(ifGeIndex)
removeInstruction(ifGeIndex)
addInstructionsWithLabels(
ifGeIndex, """
if-ge v${ifGeInstruction.registerA}, v${ifGeInstruction.registerB}, :jump
""", ExternalLabel("jump", getInstruction(endIndex))
)
}
} ?: throw RollingNumberMeasureAnimatedTextFingerprint.exception
RollingNumberMeasureTextParentFingerprint.result?.classDef?.let { parentClassDef ->
RollingNumberMeasureStaticLabelFingerprint.resolve(context, parentClassDef)
// Additional text measurement method. Used if YouTube decides not to animate the likes count
// and sometimes used for initial video load.
RollingNumberMeasureStaticLabelFingerprint.also {
it.resolve(
context,
parentResult.classDef
)
}.result?.let {
RollingNumberMeasureStaticLabelFingerprint.result?.let {
it.mutableMethod.apply {
val measureTextIndex = it.scanResult.patternScanResult!!.startIndex + 1
val freeRegister = getInstruction<TwoRegisterInstruction>(0).registerA

View File

@ -1,23 +1,34 @@
package app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.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
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
/**
* Resolves to class found in [RollingNumberMeasureTextParentFingerprint].
* Compatible with YouTube v18.30.xx to v18.49.xx
*/
object RollingNumberMeasureAnimatedTextFingerprint : MethodFingerprint(
returnType = "F",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Ljava/lang/String;"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.ADD_FLOAT_2ADDR,
Opcode.ADD_FLOAT_2ADDR, // measuredTextWidth
Opcode.ADD_INT_LIT8,
Opcode.GOTO,
Opcode.RETURN
)
Opcode.GOTO
),
customFingerprint = custom@{ methodDef, _ ->
if (methodDef.implementation == null)
return@custom false
for (instruction in methodDef.implementation!!.instructions) {
if (instruction.opcode != Opcode.INVOKE_VIRTUAL)
continue
val invokeInstruction = instruction as ReferenceInstruction
if (!invokeInstruction.reference.toString().endsWith("Landroid/text/TextPaint;->measureText([CII)F"))
continue
return@custom true
}
return@custom false
}
)