From 967c1cbd4b340a382a355f13d236d2881bafddbf Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 24 Feb 2023 05:59:07 +0400 Subject: [PATCH] feat(youtube/return-youtube-dislike): support for shorts (#1596) Signed-off-by: oSumAtrIX Co-authored-by: oSumAtrIX --- .../ShortsTextComponentParentFingerprint.kt | 25 +++++ .../patch/ReturnYouTubeDislikePatch.kt | 103 +++++++++++++----- .../videoid/fingerprint/VideoIdFingerprint.kt | 29 +++-- .../misc/video/videoid/patch/VideoIdPatch.kt | 31 ++++-- 4 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextComponentParentFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextComponentParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextComponentParentFingerprint.kt new file mode 100644 index 000000000..7179eb3ac --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextComponentParentFingerprint.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.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 ShortsTextComponentParentFingerprint : MethodFingerprint( + returnType = "V", + access = AccessFlags.PROTECTED or AccessFlags.FINAL, + parameters = listOf("L", "L"), + opcodes = listOf( + Opcode.IF_EQZ, + Opcode.CONST_4, + Opcode.IF_EQ, + Opcode.CONST_4, + Opcode.IF_EQ, + Opcode.RETURN_VOID, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQZ, + Opcode.INVOKE_STATIC + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt index 65d627465..6a63c83d0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt @@ -5,8 +5,10 @@ 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.data.toMethodWalker import app.revanced.patcher.extensions.MethodFingerprintExtensions.name import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve import app.revanced.patcher.patch.BytecodePatch @@ -15,12 +17,15 @@ import app.revanced.patcher.patch.PatchResultError 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.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.* import app.revanced.patches.youtube.layout.returnyoutubedislike.resource.patch.ReturnYouTubeDislikeResourcePatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch +import org.jf.dexlib2.builder.instruction.BuilderInstruction35c +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction @Patch @DependsOn( @@ -38,55 +43,103 @@ import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch class ReturnYouTubeDislikePatch : BytecodePatch( listOf( TextComponentSpecParentFingerprint, + ShortsTextComponentParentFingerprint, LikeFingerprint, DislikeFingerprint, RemoveLikeFingerprint, ) ) { override fun execute(context: BytecodeContext): PatchResult { + // region Inject newVideoLoaded event handler + + VideoIdPatch.injectCall("$INTEGRATIONS_PATCH_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") + + // endregion + + // region Hook interaction + listOf( LikeFingerprint.toPatch(Vote.LIKE), DislikeFingerprint.toPatch(Vote.DISLIKE), RemoveLikeFingerprint.toPatch(Vote.REMOVE_LIKE) ).forEach { (fingerprint, vote) -> - with(fingerprint.result ?: return PatchResultError("Failed to find ${fingerprint.name} method.")) { - mutableMethod.addInstructions( + fingerprint.result?.mutableMethod?.apply { + addInstructions( 0, """ const/4 v0, ${vote.value} - invoke-static {v0}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->sendVote(I)V + invoke-static {v0}, $INTEGRATIONS_PATCH_CLASS_DESCRIPTOR->sendVote(I)V """ ) - } + } ?: return PatchResultError("Failed to find ${fingerprint.name} method.") } - VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->newVideoLoaded(Ljava/lang/String;)V") + // endregion - with(TextComponentFingerprint - .apply { resolve(context, TextComponentSpecParentFingerprint.result!!.classDef) } - .result ?: return TextComponentFingerprint.toErrorResult() - ) { - val createComponentMethod = mutableMethod + // region Hook components - val conversionContextParam = 5 - val textRefParam = createComponentMethod.parameters.size - 2 - // insert index must be 0, otherwise UI does not updated correctly in some situations - // such as switching from full screen or when using previous/next overlay buttons. - val insertIndex = 0 + TextComponentFingerprint.also { it.resolve(context, TextComponentSpecParentFingerprint.result!!.classDef) } + .result?.let { + with(it.mutableMethod) { + val createComponentMethod = this + + val conversionContextParam = 5 + val textRefParam = createComponentMethod.parameters.size - 2 + // Insert index must be 0, otherwise UI does not updated correctly in some situations + // such as switching from full screen or when using previous/next overlay buttons. + val insertIndex = 0 + + createComponentMethod.addInstructions( + insertIndex, + """ + move-object/from16 v7, p$conversionContextParam + move-object/from16 v8, p$textRefParam + invoke-static {v7, v8}, $INTEGRATIONS_PATCH_CLASS_DESCRIPTOR->onComponentCreated(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;)V + """ + ) + } + } ?: return TextComponentFingerprint.toErrorResult() + + ShortsTextComponentParentFingerprint.result?.let { + context + .toMethodWalker(it.method) + .nextMethod(it.scanResult.patternScanResult!!.endIndex, true) + .getMethod().let { method -> + with(method as MutableMethod) { + // After walking, verify the found method is what's expected. + if (returnType != ("Ljava/lang/CharSequence;") || parameterTypes.size != 1) + return PatchResultError("Method signature did not match: $this $parameterTypes") + + val insertIndex = implementation!!.instructions.size - 1 + + val spannedParameterRegister = (instruction(insertIndex) as OneRegisterInstruction).registerA + val parameter = (instruction(insertIndex - 2) as BuilderInstruction35c).reference + + if (!parameter.toString().endsWith("Landroid/text/Spanned;")) + return PatchResultError("Method signature parameter did not match: $parameter") + + addInstructions( + insertIndex, + """ + invoke-static {v$spannedParameterRegister}, $INTEGRATIONS_PATCH_CLASS_DESCRIPTOR->onShortsComponentCreated(Landroid/text/Spanned;)Landroid/text/Spanned; + move-result-object v$spannedParameterRegister + """ + ) + } + } + } ?: return ShortsTextComponentParentFingerprint.toErrorResult() + + // endregion - createComponentMethod.addInstructions( - insertIndex, - """ - move-object/from16 v7, p$conversionContextParam - move-object/from16 v8, p$textRefParam - invoke-static {v7, v8}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->onComponentCreated(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;)V - """ - ) - } return PatchResultSuccess() } - private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind) + private companion object { + const val INTEGRATIONS_PATCH_CLASS_DESCRIPTOR = + "Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;" + + private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind) + } private data class VotePatch(val fingerprint: MethodFingerprint, val voteKind: Vote) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt index b688fb7a0..bed9e90d3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt @@ -1,16 +1,31 @@ package app.revanced.patches.youtube.misc.video.videoid.fingerprint import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Opcode +@FuzzyPatternScanMethod(2) object VideoIdFingerprint : MethodFingerprint( - "V", - AccessFlags.DECLARED_SYNCHRONIZED or AccessFlags.FINAL or AccessFlags.PUBLIC, - listOf("L"), - listOf(Opcode.INVOKE_INTERFACE), - customFingerprint = { - it.definingClass.endsWith("PlaybackLifecycleMonitor;") - } + returnType = "V", + access = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L"), + opcodes = listOf( + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT + ), + null, + { it.definingClass.endsWith("SubtitlesOverlayPresenter;") } ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt index da21078f2..9e6d1ed9d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt @@ -1,5 +1,6 @@ package app.revanced.patches.youtube.misc.video.videoid.patch +import app.revanced.extensions.toErrorResult import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version @@ -22,17 +23,18 @@ import org.jf.dexlib2.iface.instruction.OneRegisterInstruction @Version("0.0.1") @DependsOn([IntegrationsPatch::class]) class VideoIdPatch : BytecodePatch( - listOf( - VideoIdFingerprint - ) + listOf(VideoIdFingerprint) ) { override fun execute(context: BytecodeContext): PatchResult { - with(VideoIdFingerprint.result!!) { - insertMethod = mutableMethod - insertIndex = scanResult.patternScanResult!!.endIndex + 2 + VideoIdFingerprint.result?.let { + val videoIdRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex - videoIdRegister = (insertMethod.instruction(insertIndex - 1) as OneRegisterInstruction).registerA - } + with(it.mutableMethod) { + insertMethod = this + videoIdRegister = (instruction(videoIdRegisterInstructionIndex) as OneRegisterInstruction).registerA + insertIndex = videoIdRegisterInstructionIndex + 1 + } + } ?: return VideoIdFingerprint.toErrorResult() return PatchResultSuccess() } @@ -49,10 +51,15 @@ class VideoIdPatch : BytecodePatch( */ fun injectCall( methodDescriptor: String - ) = insertMethod.addInstructions( - insertIndex, // move-result-object offset - "invoke-static {v$videoIdRegister}, $methodDescriptor" - ) + ) { + insertMethod.addInstructions( + // TODO: The order has been proven to not be required, so remove the logic for keeping order. + // Keep injection calls in the order they're added: + // Increment index. So if additional injection calls are added, those calls run after this injection call. + insertIndex++, + "invoke-static {v$videoIdRegister}, $methodDescriptor" + ) + } } }