From c3fd36cfba7fb8246f8b3a29d9310bd88f01c3e7 Mon Sep 17 00:00:00 2001 From: LagradOst <46196380+Blatzar@users.noreply.github.com> Date: Sun, 11 Jun 2023 23:19:41 +0000 Subject: [PATCH] feat(reddit): add `hide-promoted` patch (#2350) Co-authored-by: oSumAtrIX --- .../annotations/HideBannerCompatibility.kt | 12 --- .../reddit/ad/banner/patch/HideBannerPatch.kt | 5 - .../fingerprints/HideCommentAdsFingerprint.kt | 14 +++ .../ad/comments/patch/HideCommentAdsPatch.kt | 34 +++++++ .../annotations/HideAdsCompatibility.kt | 6 +- .../general/fingerprints/AdPostFingerprint.kt | 10 ++ .../fingerprints/NewAdPostFingerprint.kt | 10 ++ .../reddit/ad/general/patch/HideAdsPatch.kt | 94 +++++++++++++------ 8 files changed, 135 insertions(+), 50 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/banner/annotations/HideBannerCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/comments/fingerprints/HideCommentAdsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/comments/patch/HideCommentAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/AdPostFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/NewAdPostFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/banner/annotations/HideBannerCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/banner/annotations/HideBannerCompatibility.kt deleted file mode 100644 index 90daa6b62..000000000 --- a/src/main/kotlin/app/revanced/patches/reddit/ad/banner/annotations/HideBannerCompatibility.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.patches.reddit.ad.banner.annotations - -import app.revanced.patcher.annotation.Compatibility -import app.revanced.patcher.annotation.Package - -@Compatibility( - [Package( - "com.reddit.frontpage" - )] -) -@Target(AnnotationTarget.CLASS) -internal annotation class HideBannerCompatibility diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/banner/patch/HideBannerPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/banner/patch/HideBannerPatch.kt index c6d06c808..927c72377 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/ad/banner/patch/HideBannerPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/banner/patch/HideBannerPatch.kt @@ -7,17 +7,12 @@ import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.ResourcePatch -import app.revanced.patcher.patch.annotations.Patch -import app.revanced.patches.reddit.ad.banner.annotations.HideBannerCompatibility -@Patch @Name("hide-subreddit-banner") @Description("Hides banner ads from comments on subreddits.") -@HideBannerCompatibility @Version("0.0.1") class HideBannerPatch : ResourcePatch { override fun execute(context: ResourceContext): PatchResult { - context.xmlEditor[RESOURCE_FILE_PATH].use { it.file.getElementsByTagName("merge").item(0).childNodes.apply { val attributes = arrayOf("height", "width") diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/comments/fingerprints/HideCommentAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/comments/fingerprints/HideCommentAdsFingerprint.kt new file mode 100644 index 000000000..a7142d290 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/comments/fingerprints/HideCommentAdsFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.reddit.ad.comments.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object HideCommentAdsFingerprint : MethodFingerprint( + strings = listOf( + "link", + // CommentPageRepository is not returning a link object + "is not returning a link object" + ), + customFingerprint = { _, classDef -> + classDef.sourceFile == "PostDetailPresenter.kt" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/comments/patch/HideCommentAdsPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/comments/patch/HideCommentAdsPatch.kt new file mode 100644 index 000000000..e14943e0c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/comments/patch/HideCommentAdsPatch.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.reddit.ad.comments.patch + +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.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.RequiresIntegrations +import app.revanced.patches.reddit.ad.comments.fingerprints.HideCommentAdsFingerprint + +@Name("hide-comment-ads") +@Description("Removes all comment ads.") +@RequiresIntegrations +@Version("0.0.1") +class HideCommentAdsPatch : BytecodePatch( + listOf(HideCommentAdsFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = HideCommentAdsFingerprint.result!!.mutableMethod + // Returns a blank object instead of the comment ad. + method.addInstructions( + 0, + """ + new-instance v0, Ljava/lang/Object; + invoke-direct {v0}, Ljava/lang/Object;->()V + return-object v0 + """ + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/HideAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/HideAdsCompatibility.kt index f8dbd2d66..cc9230c57 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/HideAdsCompatibility.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/HideAdsCompatibility.kt @@ -3,10 +3,6 @@ package app.revanced.patches.reddit.ad.general.annotations import app.revanced.patcher.annotation.Compatibility import app.revanced.patcher.annotation.Package -@Compatibility( - [Package( - "com.reddit.frontpage" - )] -) +@Compatibility([Package("com.reddit.frontpage")]) @Target(AnnotationTarget.CLASS) internal annotation class HideAdsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/AdPostFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/AdPostFingerprint.kt new file mode 100644 index 000000000..aecacc29c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/AdPostFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.reddit.ad.general.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object AdPostFingerprint : MethodFingerprint( + "V", + // "children" are present throughout multiple versions + strings = listOf("children"), + customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("Listing;") }, +) diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/NewAdPostFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/NewAdPostFingerprint.kt new file mode 100644 index 000000000..130aba52b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/fingerprints/NewAdPostFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.reddit.ad.general.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object NewAdPostFingerprint : MethodFingerprint( + opcodes = listOf(Opcode.INVOKE_VIRTUAL), + strings = listOf("chain", "feedElement"), + customFingerprint = { _, classDef -> classDef.sourceFile == "AdElementConverter.kt" }, +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/HideAdsPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/HideAdsPatch.kt index 7f58d558e..fa324a17a 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/HideAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/HideAdsPatch.kt @@ -4,51 +4,89 @@ 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.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.annotations.RequiresIntegrations +import app.revanced.patches.reddit.ad.banner.patch.HideBannerPatch +import app.revanced.patches.reddit.ad.comments.patch.HideCommentAdsPatch import app.revanced.patches.reddit.ad.general.annotations.HideAdsCompatibility +import app.revanced.patches.reddit.ad.general.fingerprints.AdPostFingerprint +import app.revanced.patches.reddit.ad.general.fingerprints.NewAdPostFingerprint import org.jf.dexlib2.Opcode -import org.jf.dexlib2.builder.instruction.BuilderInstruction21c import org.jf.dexlib2.iface.instruction.ReferenceInstruction -import org.jf.dexlib2.iface.reference.StringReference -import org.jf.dexlib2.immutable.reference.ImmutableStringReference +import org.jf.dexlib2.iface.instruction.formats.Instruction22c +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.MethodReference @Patch @Name("hide-ads") -@Description("Removes general ads from the Reddit frontpage and subreddits.") +@Description("Removes ads from the Reddit.") +@DependsOn([HideBannerPatch::class, HideCommentAdsPatch::class]) @HideAdsCompatibility -@Version("0.0.1") -class HideAdsPatch : BytecodePatch() { +@RequiresIntegrations +@Version("0.0.2") +class HideAdsPatch : BytecodePatch( + listOf(AdPostFingerprint, NewAdPostFingerprint) +) { override fun execute(context: BytecodeContext): PatchResult { - context.classes.forEach { classDef -> - classDef.methods.forEach methodLoop@{ method -> - val implementation = method.implementation ?: return@methodLoop + // region Filter promoted ads (does not work in popular or latest feed) - implementation.instructions.forEachIndexed { i, instruction -> - if (instruction.opcode != Opcode.CONST_STRING) return@forEachIndexed - if (((instruction as ReferenceInstruction).reference as StringReference).string != "AdPost") return@forEachIndexed + AdPostFingerprint.result?.mutableMethod?.apply { + val setPostsListChildren = implementation!!.instructions.first { instruction -> + if (instruction.opcode != Opcode.IPUT_OBJECT) return@first false - val proxiedClass = context.proxy(classDef).mutableClass - - val proxiedImplementation = proxiedClass.methods.first { - it.name == method.name && it.parameterTypes.containsAll(method.parameterTypes) - }.implementation!! - - var newString = "AdPost1" - if (proxiedImplementation.instructions[i - 1].opcode == Opcode.CONST_STRING) { - newString = "SubredditPost" - } - proxiedImplementation.replaceInstruction( - i, BuilderInstruction21c( - Opcode.CONST_STRING, (proxiedImplementation.instructions[i] as BuilderInstruction21c).registerA, ImmutableStringReference(newString) - ) - ) - } + val reference = (instruction as ReferenceInstruction).reference as FieldReference + reference.name == "children" } + + val castedInstruction = setPostsListChildren as Instruction22c + val itemsRegister = castedInstruction.registerA + val listInstanceRegister = castedInstruction.registerB + + // postsList.children = filterChildren(postListItems) + removeInstruction(setPostsListChildren.location.index) + addInstructions( + setPostsListChildren.location.index, + """ + invoke-static {v$itemsRegister}, $FILTER_METHOD_DESCRIPTOR + move-result-object v0 + iput-object v0, v$listInstanceRegister, ${castedInstruction.reference} + """ + ) } + // endregion + + // region Remove ads from popular and latest feed + + NewAdPostFingerprint.result?.let { result -> + // The new feeds work by inserting posts into lists. + // AdElementConverter is conveniently responsible for inserting all feed ads. + // By removing the appending instruction no ad posts gets appended to the feed. + val index = result.method.implementation!!.instructions.indexOfFirst { + if (it.opcode != Opcode.INVOKE_VIRTUAL) return@indexOfFirst false + + val reference = (it as ReferenceInstruction).reference as MethodReference + + reference.name == "add" && reference.definingClass == "Lava/util/ArrayList;" + } + + result.mutableMethod.removeInstruction(index) + } + + // endregion + return PatchResultSuccess() } + + private companion object { + private const val FILTER_METHOD_DESCRIPTOR = + "Lapp/revanced/reddit/patches/FilterPromotedLinksPatch;" + + "->filterChildren(Ljava/lang/Iterable;)Ljava/util/List;" + } }