diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 49822b6f92..5ca19d08bf 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -263,6 +263,12 @@ public final class app/revanced/patches/duolingo/debug/EnableDebugMenuPatch : ap public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + public final class app/revanced/patches/facebook/ads/story/HideStoryAdsPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/facebook/ads/story/HideStoryAdsPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V diff --git a/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch.kt b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch.kt new file mode 100644 index 0000000000..82b0bcd0a5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch.kt @@ -0,0 +1,96 @@ +package app.revanced.patches.facebook.ads.mainfeed + +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.or +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.facebook.ads.mainfeed.fingerprints.BaseModelMapperFingerprint +import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GetSponsoredDataModelTemplateFingerprint +import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GetStoryVisibilityFingerprint +import app.revanced.util.exception +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter + +@Patch( + name = "Hide 'Sponsored Stories'", + compatiblePackages = [CompatiblePackage("com.facebook.katana")], +) +@Suppress("unused") +object HideSponsoredStoriesPatch : BytecodePatch( + setOf(GetStoryVisibilityFingerprint, GetSponsoredDataModelTemplateFingerprint, BaseModelMapperFingerprint), +) { + private const val GRAPHQL_STORY_TYPE = "Lcom/facebook/graphql/model/GraphQLStory;" + + override fun execute(context: BytecodeContext) { + GetStoryVisibilityFingerprint.result?.apply { + val sponsoredDataModelTemplateMethod = GetSponsoredDataModelTemplateFingerprint.resultOrThrow().method + val baseModelMapperMethod = BaseModelMapperFingerprint.resultOrThrow().method + val baseModelWithTreeType = baseModelMapperMethod.returnType + + // The "SponsoredDataModelTemplate" methods has the ids in its body to extract sponsored data + // from GraphQL models, but targets the wrong derived type of "BaseModelWithTree". Since those ids + // could change in future version, we need to extract them and call the base implementation directly. + val getSponsoredDataHelperMethod = ImmutableMethod( + classDef.type, + "getSponsoredData", + listOf(ImmutableMethodParameter(GRAPHQL_STORY_TYPE, null, null)), + baseModelWithTreeType, + AccessFlags.PRIVATE or AccessFlags.STATIC, + null, + null, + MutableMethodImplementation(4), + ).toMutable().apply { + // Extract the ids of the original method. These ids seem to correspond to model types for + // GraphQL data structure. They are then fed to a method of BaseModelWithTree that populate + // and cast the requested GraphQL subtype. The Ids are found in the two first "CONST" instructions. + val constInstructions = sponsoredDataModelTemplateMethod.implementation!!.instructions + .asSequence() + .filterIsInstance() + .take(2) + .toList() + + val storyTypeId = constInstructions[0].narrowLiteral + val sponsoredDataTypeId = constInstructions[1].narrowLiteral + + addInstructions( + """ + const-class v2, $baseModelWithTreeType + const v1, $storyTypeId + const v0, $sponsoredDataTypeId + invoke-virtual {p0, v2, v1, v0}, $baseModelMapperMethod + move-result-object v0 + check-cast v0, $baseModelWithTreeType + return-object v0 + """, + ) + } + + mutableClass.methods.add(getSponsoredDataHelperMethod) + + // Check if the parameter type is GraphQLStory and if sponsoredDataModelGetter returns a non-null value. + // If so, hide the story by setting the visibility to StoryVisibility.GONE. + mutableMethod.addInstructionsWithLabels( + scanResult.patternScanResult!!.startIndex, + """ + instance-of v0, p0, $GRAPHQL_STORY_TYPE + if-eqz v0, :resume_normal + invoke-static {p0}, $getSponsoredDataHelperMethod + move-result-object v0 + if-eqz v0, :resume_normal + const-string v0, "GONE" + return-object v0 + :resume_normal + nop + """, + ) + } ?: throw GetStoryVisibilityFingerprint.exception + } +} diff --git a/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/BaseModelMapperFingerprint.kt b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/BaseModelMapperFingerprint.kt new file mode 100644 index 0000000000..2d6d18a9e5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/BaseModelMapperFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.facebook.ads.mainfeed.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 + +internal object BaseModelMapperFingerprint : MethodFingerprint( + + accessFlags = (AccessFlags.PUBLIC or AccessFlags.FINAL), + parameters = listOf("Ljava/lang/Class","I","I"), + returnType = "Lcom/facebook/graphql/modelutil/BaseModelWithTree;", + opcodes = listOf( + Opcode.SGET_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_4, + Opcode.IF_EQ + ) + +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetSponsoredDataModelTemplateFingerprint.kt b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetSponsoredDataModelTemplateFingerprint.kt new file mode 100644 index 0000000000..9a7384be20 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetSponsoredDataModelTemplateFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.facebook.ads.mainfeed.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 + +internal object GetSponsoredDataModelTemplateFingerprint : MethodFingerprint( + + accessFlags = (AccessFlags.PUBLIC or AccessFlags.FINAL), + parameters = listOf(), + returnType = "L", + opcodes = listOf( + Opcode.CONST, + Opcode.CONST, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.RETURN_OBJECT + ), + customFingerprint = { methodDef, classDef -> + classDef.type == "Lcom/facebook/graphql/model/GraphQLFBMultiAdsFeedUnit;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetStoryVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetStoryVisibilityFingerprint.kt new file mode 100644 index 0000000000..cf296c92e7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/facebook/ads/mainfeed/fingerprints/GetStoryVisibilityFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.facebook.ads.mainfeed.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.Annotation +import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue + +internal object GetStoryVisibilityFingerprint : MethodFingerprint( + returnType = "Ljava/lang/String;", + accessFlags = (AccessFlags.PUBLIC or AccessFlags.STATIC), + opcodes = listOf( + Opcode.INSTANCE_OF, + Opcode.IF_NEZ, + Opcode.INSTANCE_OF, + Opcode.IF_NEZ, + Opcode.INSTANCE_OF, + Opcode.IF_NEZ, + Opcode.CONST + ), + strings = listOf("This should not be called for base class object"), +) \ No newline at end of file