Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Facebook): Add Hide sponsored stories patch #3627

Merged
merged 12 commits into from
Oct 17, 2024
6 changes: 6 additions & 0 deletions api/revanced-patches.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package app.revanced.patches.facebook.ads.mainfeed

import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GetStoryVisibilityFingerprint
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GraphQLStorySponsoredDataGetterFingerprint
import app.revanced.util.resultOrThrow

@Patch(
name = "Hide 'Sponsored Stories'",
compatiblePackages = [CompatiblePackage("com.facebook.katana")]
)
@Suppress("unused")
object HideSponsoredStoriesPatch : BytecodePatch(
setOf(GetStoryVisibilityFingerprint, GraphQLStorySponsoredDataGetterFingerprint)
) {
override fun execute(context: BytecodeContext) {
GetStoryVisibilityFingerprint.result?.apply {
val sponsoredDataModelGetterMethod = GraphQLStorySponsoredDataGetterFingerprint.resultOrThrow().method

// 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, Lcom/facebook/graphql/model/GraphQLStory;
if-eqz v0, :resume_normal
invoke-virtual {p0}, $sponsoredDataModelGetterMethod
move-result-object v0
if-eqz v0, :resume_normal
const-string v0, "GONE"
return-object v0
:resume_normal
nop
"""
)
} ?: throw GetStoryVisibilityFingerprint.exception
}
}
Original file line number Diff line number Diff line change
@@ -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"),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package app.revanced.patches.facebook.ads.mainfeed.fingerprints

import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GraphQLStorySponsoredDataGetterFingerprint.SPONSORED_DATA_TYPE_ID
import app.revanced.patches.facebook.ads.mainfeed.fingerprints.GraphQLStorySponsoredDataGetterFingerprint.STORY_TYPE_ID
import com.android.tools.smali.dexlib2.iface.MethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i


internal object GraphQLStorySponsoredDataGetterFingerprint : MethodFingerprint(
customFingerprint = { methodDef, classDef ->
fun isMatchingLiteralValue(index: Int, literal: Int): Boolean {
val instruction = methodDef.implementation?.instructions?.elementAtOrNull(index) as? Instruction31i

return instruction?.narrowLiteral == literal
}

// All methods within the target class are virtually identical apart from two magic constants on each of them.
// They all return an instance of the same model but filled with different GraphQL child node data.
// Likely, the constants are the parent and child node ids.
classDef.type == "Lcom/facebook/graphql/model/GraphQLStory;" &&
isMatchingLiteralValue(1, STORY_TYPE_ID) &&
isMatchingLiteralValue(2, SPONSORED_DATA_TYPE_ID)
}
) {
// These constants trace back to some GraphQL node type descriptor.
private const val STORY_TYPE_ID = -132939024
private const val SPONSORED_DATA_TYPE_ID = 341202575
}
Loading