From 55a5ec16a7f968b6c055f83e83ca366c6f86989a Mon Sep 17 00:00:00 2001 From: leumasme Date: Sat, 23 Sep 2023 21:22:13 +0200 Subject: [PATCH 1/7] feat(Tumblr): add timeline filter patch --- .../timelinefilter/TimelineFilterPatch.kt | 68 +++++++++++++++++++ .../PostsResponseConstructorFingerprint.kt | 12 ++++ .../TimelineConstructorFingerprint.kt | 11 +++ .../TimelineFilterIntegrationFingerprint.kt | 19 ++++++ 4 files changed, 110 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt new file mode 100644 index 0000000000..c7a127d3d4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -0,0 +1,68 @@ +package app.revanced.patches.tumblr.timelinefilter + +import app.revanced.extensions.exception +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.removeInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.tumblr.timelinefilter.fingerprints.PostsResponseConstructorFingerprint +import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineConstructorFingerprint +import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineFilterIntegrationFingerprint + +@Patch(description = "Filter what will be shown in the timeline.", requiresIntegrations = true) +object TimelineFilterPatch : BytecodePatch( + setOf(TimelineConstructorFingerprint, TimelineFilterIntegrationFingerprint, PostsResponseConstructorFingerprint) +) { + /** + * Add a filter to hide the given timeline object type. + * The list of all Timeline object types is found in the TimelineObjectType class, + * where they are mapped from their api name (returned by tumblr via the HTTP API) to the enum value name. + * + * @param typename The enum name of the timeline object type to hide. + */ + @Suppress("KDocUnresolvedReference") + internal lateinit var addObjectTypeFilter: (typename: String) -> Unit private set + + override fun execute(context: BytecodeContext) { + + TimelineFilterIntegrationFingerprint.result?.let { integration -> + val startIndex = integration.scanResult.patternScanResult!!.startIndex + + integration.mutableMethod.apply { + // Remove "BLOCKED_OBJECT_DUMMY" object type filter + removeInstructions(startIndex, 5) + + addObjectTypeFilter = { typename -> + // It's too much of a pain to find the register numbers manually, so this will just have to be + // updated if the Timeline Filter integration changes + // The java equivalent of this is + // if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); + addInstructionsWithLabels( + startIndex, """ + const-string v1, "$typename" + invoke-virtual { v1, v0 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + move-result v1 + if-eqz v1, :dont_remove + invoke-interface { v2 }, Ljava/util/Iterator;->remove()V + :dont_remove + nop + """ + ) + } + } + } ?: throw TimelineFilterIntegrationFingerprint.exception + + TimelineConstructorFingerprint.result?.mutableMethod?.addInstructions( + 0, """ + invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V + """ + ) ?: throw TimelineConstructorFingerprint.exception + PostsResponseConstructorFingerprint.result?.mutableMethod?.addInstructions( + 0, """ + invoke-static {p2}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V + """ + ) ?: throw PostsResponseConstructorFingerprint.exception + } +} diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt new file mode 100644 index 0000000000..0da871f4ce --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.tumblr.timelinefilter.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +// This is the constructor of the PostsResponse class. +// The same applies here as with the TimelineConstructorFingerprint. +object PostsResponseConstructorFingerprint : MethodFingerprint( + accessFlags = AccessFlags.CONSTRUCTOR or AccessFlags.PUBLIC, + customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/PostResponse;") && methodDef.parameters.size == 4 }, +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt new file mode 100644 index 0000000000..ff400e813d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.tumblr.timelinefilter.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +// This is the constructor of the Timeline class. +// It receives the List as an argument with a @Json annotation, so this should be the first time +// that the List is exposed in non-library code. +object TimelineConstructorFingerprint : MethodFingerprint( + customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/Timeline;") }, + strings = listOf("timelineObjectsList") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt new file mode 100644 index 0000000000..cbb680f0fd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.tumblr.timelinefilter.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +// This fingerprints the Integration TimelineFilterPatch.filterTimeline method. +// The opcode fingerprint is searching for +// if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); +object TimelineFilterIntegrationFingerprint : MethodFingerprint( + customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/TimelineFilterPatch;") }, + strings = listOf("BLOCKED_OBJECT_DUMMY"), + opcodes = listOf( + Opcode.CONST_STRING, // "BLOCKED_OBJECT_DUMMY" + Opcode.INVOKE_VIRTUAL, // ^.equals(elementType) + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, // Jump to start of loop if not equals + Opcode.INVOKE_INTERFACE // iterator.remove() + ) +) \ No newline at end of file From 7fb38d9bc7cd206a6c829e023a7a6df9121c136d Mon Sep 17 00:00:00 2001 From: leumasme Date: Sat, 23 Sep 2023 21:23:37 +0200 Subject: [PATCH 2/7] refactor(Tumblr): change patches to use timeline filter --- .../patches/tumblr/ads/DisableDashboardAds.kt | 47 +++++++++++-------- .../fingerprints/AdWaterfallFingerprint.kt | 12 ----- .../tumblr/live/DisableTumblrLivePatch.kt | 29 ++++-------- .../fingerprints/LiveMarqueeFingerprint.kt | 6 --- 4 files changed, 36 insertions(+), 58 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patches/tumblr/ads/fingerprints/AdWaterfallFingerprint.kt delete mode 100644 src/main/kotlin/app/revanced/patches/tumblr/live/fingerprints/LiveMarqueeFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt index 8c8c6de8ab..fc81ce8178 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt @@ -1,33 +1,40 @@ package app.revanced.patches.tumblr.ads -import app.revanced.extensions.exception import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.tumblr.ads.fingerprints.AdWaterfallFingerprint -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch @Patch( name = "Disable dashboard ads", description = "Disables ads in the dashboard.", - compatiblePackages = [CompatiblePackage("com.tumblr")] + compatiblePackages = [CompatiblePackage("com.tumblr")], + dependencies = [TimelineFilterPatch::class] ) @Suppress("unused") -object DisableDashboardAds : BytecodePatch( - setOf(AdWaterfallFingerprint) -) { - override fun execute(context: BytecodeContext) = AdWaterfallFingerprint.result?.let { - it.scanResult.stringsScanResult!!.matches.forEach { match -> - // We just replace all occurrences of "client_side_ad_waterfall" with anything else - // so the app fails to handle ads in the timeline elements array and just skips them. - // See AdWaterfallFingerprint for more info. - val stringRegister = it.mutableMethod.getInstruction(match.index).registerA - it.mutableMethod.replaceInstruction( - match.index, "const-string v$stringRegister, \"dummy\"" - ) - } - } ?: throw AdWaterfallFingerprint.exception +object DisableDashboardAds : BytecodePatch() { + override fun execute(context: BytecodeContext) { + // Called "client_side_ad_waterfall" in api response + TimelineFilterPatch.addObjectTypeFilter("CLIENT_SIDE_MEDIATION") + // Called "backfill_ad" in api response + TimelineFilterPatch.addObjectTypeFilter("GEMINI_AD") + + // The below object types weren't actually spotted in the wild in testing, but they are valid Object types + // and their names clearly indicate that they are ads, so we just block them anyway, + // just in case they will be used in the future. + + // Called "nimbus_ad" in api response + TimelineFilterPatch.addObjectTypeFilter("NIMBUS_AD") + // Called "client_side_ad" in api response + TimelineFilterPatch.addObjectTypeFilter("CLIENT_SIDE_AD") + // Called "display_io_interscroller" in api response + TimelineFilterPatch.addObjectTypeFilter("DISPLAY_IO_INTERSCROLLER_AD") + // Called "display_io_headline_video" in api response + TimelineFilterPatch.addObjectTypeFilter("DISPLAY_IO_HEADLINE_VIDEO_AD") + // Called "facebook_biddable_sdk_ad" in api response + TimelineFilterPatch.addObjectTypeFilter("FACEBOOK_BIDDAABLE") + // Called "google_native_ad" in api response + TimelineFilterPatch.addObjectTypeFilter("GOOGLE_NATIVE") + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/ads/fingerprints/AdWaterfallFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/ads/fingerprints/AdWaterfallFingerprint.kt deleted file mode 100644 index b7737a6c0c..0000000000 --- a/src/main/kotlin/app/revanced/patches/tumblr/ads/fingerprints/AdWaterfallFingerprint.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.patches.tumblr.ads.fingerprints - -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint - -// The Tumblr app sends a request to /v2/timeline/dashboard which replies with an array of elements -// to show in the user dashboard. These element have different type ids (post, title, carousel, etc.) -// The standard dashboard Ad has the id client_side_ad_waterfall, and this string has to be in the code -// to handle ads and provide their appearance. -// If we just replace this string in the tumblr code with anything else, it will fail to recognize the -// dashboard object type and just skip it. This is a bit weird, but it shouldn't break -// unless they change the api (unlikely) or explicitly Change the tumblr code to prevent this. -object AdWaterfallFingerprint : MethodFingerprint(strings = listOf("client_side_ad_waterfall")) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt index f588d02a25..1c9ec2a836 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt @@ -1,37 +1,26 @@ package app.revanced.patches.tumblr.live -import app.revanced.extensions.exception import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch -import app.revanced.patches.tumblr.live.fingerprints.LiveMarqueeFingerprint -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch @Patch( name = "Disable Tumblr Live", description = "Disable the Tumblr Live tab button and dashboard carousel.", - dependencies = [OverrideFeatureFlagsPatch::class], + dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class], compatiblePackages = [CompatiblePackage("com.tumblr")] ) @Suppress("unused") -object DisableTumblrLivePatch : BytecodePatch( - setOf(LiveMarqueeFingerprint) -) { - override fun execute(context: BytecodeContext) = LiveMarqueeFingerprint.result?.let { - it.scanResult.stringsScanResult!!.matches.forEach { match -> - // Replace the string constant "live_marquee" - // with a dummy so the app doesn't recognize this type of element in the Dashboard and skips it - it.mutableMethod.apply { - val stringRegister = getInstruction(match.index).registerA - replaceInstruction(match.index, "const-string v$stringRegister, \"dummy2\"") - } - } +object DisableTumblrLivePatch : BytecodePatch() { + override fun execute(context: BytecodeContext) { + // Hide the LIVE_MARQUEE timeline element that appears in the feed + // Called "live_marquee" in api response + TimelineFilterPatch.addObjectTypeFilter("LIVE_MARQUEE") - // We hide the Tab button for Tumblr Live by forcing the feature flag to false + // Hide the Tab button for Tumblr Live by forcing the feature flag to false OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false") - } ?: throw LiveMarqueeFingerprint.exception + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/live/fingerprints/LiveMarqueeFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/live/fingerprints/LiveMarqueeFingerprint.kt deleted file mode 100644 index 3f4da4b004..0000000000 --- a/src/main/kotlin/app/revanced/patches/tumblr/live/fingerprints/LiveMarqueeFingerprint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.revanced.patches.tumblr.live.fingerprints - -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint - -// This works identically to the Tumblr AdWaterfallFingerprint, see comments there -object LiveMarqueeFingerprint : MethodFingerprint(strings = listOf("live_marquee")) \ No newline at end of file From 842286843c4be68a2f48a98d485dd19fb759832e Mon Sep 17 00:00:00 2001 From: leumasme Date: Sun, 24 Sep 2023 00:18:18 +0200 Subject: [PATCH 3/7] refactor(Tumblr): address requested changes --- .../patches/tumblr/ads/DisableDashboardAds.kt | 37 +++++++++---------- .../timelinefilter/TimelineFilterPatch.kt | 26 ++++++------- .../TimelineConstructorFingerprint.kt | 5 ++- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt index fc81ce8178..737c0d885e 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt @@ -15,26 +15,23 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch @Suppress("unused") object DisableDashboardAds : BytecodePatch() { override fun execute(context: BytecodeContext) { - // Called "client_side_ad_waterfall" in api response - TimelineFilterPatch.addObjectTypeFilter("CLIENT_SIDE_MEDIATION") - // Called "backfill_ad" in api response - TimelineFilterPatch.addObjectTypeFilter("GEMINI_AD") + // The timeline object types are filtered by their name in the TimelineObjectType enum. + // This is often different from the "object_type" returned in the api (noted in comments here) + arrayOf( + "CLIENT_SIDE_MEDIATION", // "client_side_ad_waterfall" + "GEMINI_AD", // "backfill_ad" - // The below object types weren't actually spotted in the wild in testing, but they are valid Object types - // and their names clearly indicate that they are ads, so we just block them anyway, - // just in case they will be used in the future. - - // Called "nimbus_ad" in api response - TimelineFilterPatch.addObjectTypeFilter("NIMBUS_AD") - // Called "client_side_ad" in api response - TimelineFilterPatch.addObjectTypeFilter("CLIENT_SIDE_AD") - // Called "display_io_interscroller" in api response - TimelineFilterPatch.addObjectTypeFilter("DISPLAY_IO_INTERSCROLLER_AD") - // Called "display_io_headline_video" in api response - TimelineFilterPatch.addObjectTypeFilter("DISPLAY_IO_HEADLINE_VIDEO_AD") - // Called "facebook_biddable_sdk_ad" in api response - TimelineFilterPatch.addObjectTypeFilter("FACEBOOK_BIDDAABLE") - // Called "google_native_ad" in api response - TimelineFilterPatch.addObjectTypeFilter("GOOGLE_NATIVE") + // The below object types weren't actually spotted in the wild in testing, but they are valid Object types + // and their names clearly indicate that they are ads, so we just block them anyway, + // just in case they will be used in the future. + "NIMBUS_AD", // "nimbus_ad" + "CLIENT_SIDE_AD", // "client_side_ad" + "DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller" + "DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video" + "FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad" + "GOOGLE_NATIVE" // "google_native_ad" + ).forEach { + TimelineFilterPatch.addObjectTypeFilter(it) + } } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt index c7a127d3d4..4ba1d30051 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -11,7 +11,7 @@ import app.revanced.patches.tumblr.timelinefilter.fingerprints.PostsResponseCons import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineConstructorFingerprint import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineFilterIntegrationFingerprint -@Patch(description = "Filter what will be shown in the timeline.", requiresIntegrations = true) +@Patch(description = "Filter timeline objects.", requiresIntegrations = true) object TimelineFilterPatch : BytecodePatch( setOf(TimelineConstructorFingerprint, TimelineFilterIntegrationFingerprint, PostsResponseConstructorFingerprint) ) { @@ -20,28 +20,28 @@ object TimelineFilterPatch : BytecodePatch( * The list of all Timeline object types is found in the TimelineObjectType class, * where they are mapped from their api name (returned by tumblr via the HTTP API) to the enum value name. * - * @param typename The enum name of the timeline object type to hide. + * @param typeName The enum name of the timeline object type to hide. */ @Suppress("KDocUnresolvedReference") - internal lateinit var addObjectTypeFilter: (typename: String) -> Unit private set + internal lateinit var addObjectTypeFilter: (typeName: String) -> Unit private set override fun execute(context: BytecodeContext) { TimelineFilterIntegrationFingerprint.result?.let { integration -> - val startIndex = integration.scanResult.patternScanResult!!.startIndex + val filterInsertIndex = integration.scanResult.patternScanResult!!.startIndex integration.mutableMethod.apply { // Remove "BLOCKED_OBJECT_DUMMY" object type filter - removeInstructions(startIndex, 5) + removeInstructions(filterInsertIndex, 5) - addObjectTypeFilter = { typename -> + addObjectTypeFilter = { typeName -> // It's too much of a pain to find the register numbers manually, so this will just have to be // updated if the Timeline Filter integration changes // The java equivalent of this is // if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); addInstructionsWithLabels( - startIndex, """ - const-string v1, "$typename" + filterInsertIndex, """ + const-string v1, "$typeName" invoke-virtual { v1, v0 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z move-result v1 if-eqz v1, :dont_remove @@ -55,14 +55,12 @@ object TimelineFilterPatch : BytecodePatch( } ?: throw TimelineFilterIntegrationFingerprint.exception TimelineConstructorFingerprint.result?.mutableMethod?.addInstructions( - 0, """ - invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V - """ + 0, + "invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V" ) ?: throw TimelineConstructorFingerprint.exception PostsResponseConstructorFingerprint.result?.mutableMethod?.addInstructions( - 0, """ - invoke-static {p2}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V - """ + 0, + "invoke-static {p2}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V" ) ?: throw PostsResponseConstructorFingerprint.exception } } diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt index ff400e813d..a8f59c4168 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineConstructorFingerprint.kt @@ -6,6 +6,7 @@ import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint // It receives the List as an argument with a @Json annotation, so this should be the first time // that the List is exposed in non-library code. object TimelineConstructorFingerprint : MethodFingerprint( - customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/Timeline;") }, - strings = listOf("timelineObjectsList") + customFingerprint = { methodDef, _ -> + methodDef.definingClass.endsWith("/Timeline;") && methodDef.parameters[0].type == "Ljava/util/List;" + }, strings = listOf("timelineObjectsList") ) \ No newline at end of file From 2c1239c6028e9800bd3ccba634a7fd0c3d550e0e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 28 Sep 2023 05:08:02 +0200 Subject: [PATCH 4/7] minor refactor --- .../patches/tumblr/ads/DisableDashboardAds.kt | 2 +- .../timelinefilter/TimelineFilterPatch.kt | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt index 737c0d885e..09972604a6 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/ads/DisableDashboardAds.kt @@ -21,7 +21,7 @@ object DisableDashboardAds : BytecodePatch() { "CLIENT_SIDE_MEDIATION", // "client_side_ad_waterfall" "GEMINI_AD", // "backfill_ad" - // The below object types weren't actually spotted in the wild in testing, but they are valid Object types + // The object types below weren't actually spotted in the wild in testing, but they are valid Object types // and their names clearly indicate that they are ads, so we just block them anyway, // just in case they will be used in the future. "NIMBUS_AD", // "nimbus_ad" diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt index 4ba1d30051..9c8c51bb7a 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -35,8 +35,6 @@ object TimelineFilterPatch : BytecodePatch( removeInstructions(filterInsertIndex, 5) addObjectTypeFilter = { typeName -> - // It's too much of a pain to find the register numbers manually, so this will just have to be - // updated if the Timeline Filter integration changes // The java equivalent of this is // if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); addInstructionsWithLabels( @@ -54,13 +52,12 @@ object TimelineFilterPatch : BytecodePatch( } } ?: throw TimelineFilterIntegrationFingerprint.exception - TimelineConstructorFingerprint.result?.mutableMethod?.addInstructions( - 0, - "invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V" - ) ?: throw TimelineConstructorFingerprint.exception - PostsResponseConstructorFingerprint.result?.mutableMethod?.addInstructions( - 0, - "invoke-static {p2}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->filterTimeline(Ljava/util/List;)V" - ) ?: throw PostsResponseConstructorFingerprint.exception + arrayOf(TimelineConstructorFingerprint, PostsResponseConstructorFingerprint).forEach { + it.result?.mutableMethod?.addInstructions( + 0, + "invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->" + + "filterTimeline(Ljava/util/List;)V" + ) ?: throw it.exception + } } } From 920b9644d5eeb56311e7c230208d41fb883a0aeb Mon Sep 17 00:00:00 2001 From: leumasme Date: Fri, 29 Sep 2023 06:24:56 +0200 Subject: [PATCH 5/7] refactor: update tumblr integration injection method --- .../timelinefilter/TimelineFilterPatch.kt | 35 ++++++++++++------- .../PostsResponseConstructorFingerprint.kt | 2 +- .../TimelineFilterIntegrationFingerprint.kt | 5 +-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt index 9c8c51bb7a..8f57d55787 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -4,12 +4,14 @@ import app.revanced.extensions.exception 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.removeInstructions import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.tumblr.timelinefilter.fingerprints.PostsResponseConstructorFingerprint import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineConstructorFingerprint import app.revanced.patches.tumblr.timelinefilter.fingerprints.TimelineFilterIntegrationFingerprint +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c @Patch(description = "Filter timeline objects.", requiresIntegrations = true) object TimelineFilterPatch : BytecodePatch( @@ -31,33 +33,40 @@ object TimelineFilterPatch : BytecodePatch( val filterInsertIndex = integration.scanResult.patternScanResult!!.startIndex integration.mutableMethod.apply { + // This is the List.add call from the dummy object type filter + val instr = getInstruction(filterInsertIndex + 1) + + assert(instr.registerCount == 2) + + // From the dummy filter call, we can get the 2 registers we need to add more filters + val listRegister = instr.registerC + val stringRegister = instr.registerD + // Remove "BLOCKED_OBJECT_DUMMY" object type filter - removeInstructions(filterInsertIndex, 5) + removeInstructions(filterInsertIndex, 2) addObjectTypeFilter = { typeName -> // The java equivalent of this is - // if ("BLOCKED_OBJECT_DUMMY".equals(elementType)) iterator.remove(); + // blockedObjectTypes.add({typeName}) addInstructionsWithLabels( filterInsertIndex, """ - const-string v1, "$typeName" - invoke-virtual { v1, v0 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z - move-result v1 - if-eqz v1, :dont_remove - invoke-interface { v2 }, Ljava/util/Iterator;->remove()V - :dont_remove - nop + const-string v$stringRegister, "$typeName" + invoke-interface { v$listRegister, v$stringRegister }, Ljava/util/List;->add(Ljava/lang/Object;)Z """ ) } } } ?: throw TimelineFilterIntegrationFingerprint.exception - arrayOf(TimelineConstructorFingerprint, PostsResponseConstructorFingerprint).forEach { - it.result?.mutableMethod?.addInstructions( + mapOf( + TimelineConstructorFingerprint to 1, + PostsResponseConstructorFingerprint to 2 + ).forEach { (fingerprint, paramRegister) -> + fingerprint.result?.mutableMethod?.addInstructions( 0, - "invoke-static {p1}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->" + + "invoke-static {p$paramRegister}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->" + "filterTimeline(Ljava/util/List;)V" - ) ?: throw it.exception + ) ?: throw fingerprint.exception } } } diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt index 0da871f4ce..cab0a3c302 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/PostsResponseConstructorFingerprint.kt @@ -8,5 +8,5 @@ import com.android.tools.smali.dexlib2.AccessFlags // The same applies here as with the TimelineConstructorFingerprint. object PostsResponseConstructorFingerprint : MethodFingerprint( accessFlags = AccessFlags.CONSTRUCTOR or AccessFlags.PUBLIC, - customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/PostResponse;") && methodDef.parameters.size == 4 }, + customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/PostsResponse;") && methodDef.parameters.size == 4 }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt index cbb680f0fd..2b08f503c8 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/fingerprints/TimelineFilterIntegrationFingerprint.kt @@ -11,9 +11,6 @@ object TimelineFilterIntegrationFingerprint : MethodFingerprint( strings = listOf("BLOCKED_OBJECT_DUMMY"), opcodes = listOf( Opcode.CONST_STRING, // "BLOCKED_OBJECT_DUMMY" - Opcode.INVOKE_VIRTUAL, // ^.equals(elementType) - Opcode.MOVE_RESULT, - Opcode.IF_EQZ, // Jump to start of loop if not equals - Opcode.INVOKE_INTERFACE // iterator.remove() + Opcode.INVOKE_INTERFACE // List.add(^) ) ) \ No newline at end of file From eafd840d5b39d0b6a782bc3fa8dbbcec4aecd464 Mon Sep 17 00:00:00 2001 From: leumasme Date: Fri, 29 Sep 2023 15:48:39 +0200 Subject: [PATCH 6/7] refactor: change assert into manual throw --- .../patches/tumblr/timelinefilter/TimelineFilterPatch.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt index 8f57d55787..c40b0cd425 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -36,7 +36,7 @@ object TimelineFilterPatch : BytecodePatch( // This is the List.add call from the dummy object type filter val instr = getInstruction(filterInsertIndex + 1) - assert(instr.registerCount == 2) + if (instr.registerCount != 2) throw TimelineFilterIntegrationFingerprint.exception // From the dummy filter call, we can get the 2 registers we need to add more filters val listRegister = instr.registerC From d206443052827620a9bbd2e569197559406d41c8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 1 Oct 2023 04:57:26 +0200 Subject: [PATCH 7/7] perf: Use HashSet --- .../timelinefilter/TimelineFilterPatch.kt | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt index c40b0cd425..a919124c07 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/timelinefilter/TimelineFilterPatch.kt @@ -28,30 +28,25 @@ object TimelineFilterPatch : BytecodePatch( internal lateinit var addObjectTypeFilter: (typeName: String) -> Unit private set override fun execute(context: BytecodeContext) { - TimelineFilterIntegrationFingerprint.result?.let { integration -> val filterInsertIndex = integration.scanResult.patternScanResult!!.startIndex integration.mutableMethod.apply { - // This is the List.add call from the dummy object type filter - val instr = getInstruction(filterInsertIndex + 1) - - if (instr.registerCount != 2) throw TimelineFilterIntegrationFingerprint.exception + val addInstruction = getInstruction(filterInsertIndex + 1) + if (addInstruction.registerCount != 2) throw TimelineFilterIntegrationFingerprint.exception - // From the dummy filter call, we can get the 2 registers we need to add more filters - val listRegister = instr.registerC - val stringRegister = instr.registerD + val filterListRegister = addInstruction.registerC + val stringRegister = addInstruction.registerD - // Remove "BLOCKED_OBJECT_DUMMY" object type filter + // Remove "BLOCKED_OBJECT_DUMMY" removeInstructions(filterInsertIndex, 2) addObjectTypeFilter = { typeName -> - // The java equivalent of this is - // blockedObjectTypes.add({typeName}) + // blockedObjectTypes.add({typeName}) addInstructionsWithLabels( filterInsertIndex, """ const-string v$stringRegister, "$typeName" - invoke-interface { v$listRegister, v$stringRegister }, Ljava/util/List;->add(Ljava/lang/Object;)Z + invoke-interface { v$filterListRegister, v$stringRegister }, Ljava/util/HashSet;->add(Ljava/lang/Object;)Z """ ) } @@ -61,10 +56,11 @@ object TimelineFilterPatch : BytecodePatch( mapOf( TimelineConstructorFingerprint to 1, PostsResponseConstructorFingerprint to 2 - ).forEach { (fingerprint, paramRegister) -> + ).forEach { (fingerprint, timelineObjectsRegister) -> fingerprint.result?.mutableMethod?.addInstructions( 0, - "invoke-static {p$paramRegister}, Lapp/revanced/tumblr/patches/TimelineFilterPatch;->" + + "invoke-static {p$timelineObjectsRegister}, " + + "Lapp/revanced/tumblr/patches/TimelineFilterPatch;->" + "filterTimeline(Ljava/util/List;)V" ) ?: throw fingerprint.exception }