Skip to content

Commit fc7644d

Browse files
feat(Facebook): Add Hide sponsored stories patch (#3627)
Co-authored-by: oSumAtrIX <[email protected]>
1 parent 061ebcb commit fc7644d

File tree

5 files changed

+169
-0
lines changed

5 files changed

+169
-0
lines changed

api/revanced-patches.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ public final class app/revanced/patches/duolingo/debug/EnableDebugMenuPatch : ap
263263
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
264264
}
265265

266+
public final class app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch : app/revanced/patcher/patch/BytecodePatch {
267+
public static final field INSTANCE Lapp/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatch;
268+
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
269+
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
270+
}
271+
266272
public final class app/revanced/patches/facebook/ads/story/HideStoryAdsPatch : app/revanced/patcher/patch/BytecodePatch {
267273
public static final field INSTANCE Lapp/revanced/patches/facebook/ads/story/HideStoryAdsPatch;
268274
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package app.revanced.patches.facebook.ads.mainfeed
2+
3+
import app.revanced.patcher.data.BytecodeContext
4+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
5+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
6+
import app.revanced.patcher.extensions.or
7+
import app.revanced.patcher.patch.BytecodePatch
8+
import app.revanced.patcher.patch.annotation.CompatiblePackage
9+
import app.revanced.patcher.patch.annotation.Patch
10+
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
11+
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.BaseModelMapperFingerprint
12+
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GetSponsoredDataModelTemplateFingerprint
13+
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GetStoryVisibilityFingerprint
14+
import app.revanced.util.exception
15+
import app.revanced.util.resultOrThrow
16+
import com.android.tools.smali.dexlib2.AccessFlags
17+
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
18+
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
19+
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
20+
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
21+
22+
@Patch(
23+
name = "Hide 'Sponsored Stories'",
24+
compatiblePackages = [CompatiblePackage("com.facebook.katana")],
25+
)
26+
@Suppress("unused")
27+
object HideSponsoredStoriesPatch : BytecodePatch(
28+
setOf(GetStoryVisibilityFingerprint, GetSponsoredDataModelTemplateFingerprint, BaseModelMapperFingerprint),
29+
) {
30+
private const val GRAPHQL_STORY_TYPE = "Lcom/facebook/graphql/model/GraphQLStory;"
31+
32+
override fun execute(context: BytecodeContext) {
33+
GetStoryVisibilityFingerprint.result?.apply {
34+
val sponsoredDataModelTemplateMethod = GetSponsoredDataModelTemplateFingerprint.resultOrThrow().method
35+
val baseModelMapperMethod = BaseModelMapperFingerprint.resultOrThrow().method
36+
val baseModelWithTreeType = baseModelMapperMethod.returnType
37+
38+
// The "SponsoredDataModelTemplate" methods has the ids in its body to extract sponsored data
39+
// from GraphQL models, but targets the wrong derived type of "BaseModelWithTree". Since those ids
40+
// could change in future version, we need to extract them and call the base implementation directly.
41+
val getSponsoredDataHelperMethod = ImmutableMethod(
42+
classDef.type,
43+
"getSponsoredData",
44+
listOf(ImmutableMethodParameter(GRAPHQL_STORY_TYPE, null, null)),
45+
baseModelWithTreeType,
46+
AccessFlags.PRIVATE or AccessFlags.STATIC,
47+
null,
48+
null,
49+
MutableMethodImplementation(4),
50+
).toMutable().apply {
51+
// Extract the ids of the original method. These ids seem to correspond to model types for
52+
// GraphQL data structure. They are then fed to a method of BaseModelWithTree that populate
53+
// and cast the requested GraphQL subtype. The Ids are found in the two first "CONST" instructions.
54+
val constInstructions = sponsoredDataModelTemplateMethod.implementation!!.instructions
55+
.asSequence()
56+
.filterIsInstance<Instruction31i>()
57+
.take(2)
58+
.toList()
59+
60+
val storyTypeId = constInstructions[0].narrowLiteral
61+
val sponsoredDataTypeId = constInstructions[1].narrowLiteral
62+
63+
addInstructions(
64+
"""
65+
const-class v2, $baseModelWithTreeType
66+
const v1, $storyTypeId
67+
const v0, $sponsoredDataTypeId
68+
invoke-virtual {p0, v2, v1, v0}, $baseModelMapperMethod
69+
move-result-object v0
70+
check-cast v0, $baseModelWithTreeType
71+
return-object v0
72+
""",
73+
)
74+
}
75+
76+
mutableClass.methods.add(getSponsoredDataHelperMethod)
77+
78+
// Check if the parameter type is GraphQLStory and if sponsoredDataModelGetter returns a non-null value.
79+
// If so, hide the story by setting the visibility to StoryVisibility.GONE.
80+
mutableMethod.addInstructionsWithLabels(
81+
scanResult.patternScanResult!!.startIndex,
82+
"""
83+
instance-of v0, p0, $GRAPHQL_STORY_TYPE
84+
if-eqz v0, :resume_normal
85+
invoke-static {p0}, $getSponsoredDataHelperMethod
86+
move-result-object v0
87+
if-eqz v0, :resume_normal
88+
const-string v0, "GONE"
89+
return-object v0
90+
:resume_normal
91+
nop
92+
""",
93+
)
94+
} ?: throw GetStoryVisibilityFingerprint.exception
95+
}
96+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package app.revanced.patches.facebook.ads.mainfeed.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
8+
internal object BaseModelMapperFingerprint : MethodFingerprint(
9+
10+
accessFlags = (AccessFlags.PUBLIC or AccessFlags.FINAL),
11+
parameters = listOf("Ljava/lang/Class","I","I"),
12+
returnType = "Lcom/facebook/graphql/modelutil/BaseModelWithTree;",
13+
opcodes = listOf(
14+
Opcode.SGET_OBJECT,
15+
Opcode.INVOKE_STATIC,
16+
Opcode.MOVE_RESULT_OBJECT,
17+
Opcode.CONST_4,
18+
Opcode.IF_EQ
19+
)
20+
21+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package app.revanced.patches.facebook.ads.mainfeed.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
8+
internal object GetSponsoredDataModelTemplateFingerprint : MethodFingerprint(
9+
10+
accessFlags = (AccessFlags.PUBLIC or AccessFlags.FINAL),
11+
parameters = listOf(),
12+
returnType = "L",
13+
opcodes = listOf(
14+
Opcode.CONST,
15+
Opcode.CONST,
16+
Opcode.INVOKE_STATIC,
17+
Opcode.MOVE_RESULT_OBJECT,
18+
Opcode.RETURN_OBJECT
19+
),
20+
customFingerprint = { methodDef, classDef ->
21+
classDef.type == "Lcom/facebook/graphql/model/GraphQLFBMultiAdsFeedUnit;"
22+
}
23+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package app.revanced.patches.facebook.ads.mainfeed.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
import com.android.tools.smali.dexlib2.iface.Annotation
8+
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
9+
10+
internal object GetStoryVisibilityFingerprint : MethodFingerprint(
11+
returnType = "Ljava/lang/String;",
12+
accessFlags = (AccessFlags.PUBLIC or AccessFlags.STATIC),
13+
opcodes = listOf(
14+
Opcode.INSTANCE_OF,
15+
Opcode.IF_NEZ,
16+
Opcode.INSTANCE_OF,
17+
Opcode.IF_NEZ,
18+
Opcode.INSTANCE_OF,
19+
Opcode.IF_NEZ,
20+
Opcode.CONST
21+
),
22+
strings = listOf("This should not be called for base class object"),
23+
)

0 commit comments

Comments
 (0)