feat(reddit): add reddit patches

This commit is contained in:
inotia00 2023-06-23 18:48:11 +09:00
parent 2f7cdc86c8
commit 10733c7a19
11 changed files with 362 additions and 0 deletions

View File

@ -0,0 +1,44 @@
package app.revanced.patches.reddit.ad.banner.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch
@Name("hide-subreddit-banner")
@Description("Hides banner ads from comments on subreddits.")
@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")
for (i in 1 until length) {
val view = item(i)
if (
view.hasAttributes() &&
view.attributes.getNamedItem("android:id").nodeValue.endsWith("ad_view_stub")
) {
attributes.forEach { attribute ->
view.attributes.getNamedItem("android:layout_$attribute").nodeValue =
"0.0dip"
}
break
}
}
}
}
return PatchResultSuccess()
}
private companion object {
const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml"
}
}

View File

@ -0,0 +1,11 @@
package app.revanced.patches.reddit.ad.comments.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
object HideCommentAdsFingerprint : MethodFingerprint(
returnType = "Ljava/lang/Object;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
customFingerprint = { it, _ -> it.definingClass.endsWith("RedditCommentsPageAdRepository;") },
)

View File

@ -0,0 +1,38 @@
package app.revanced.patches.reddit.ad.comments.patch
import app.revanced.extensions.toErrorResult
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 {
HideCommentAdsFingerprint.result?.let {
it.mutableMethod.apply {
addInstructions(
0,
"""
new-instance v0, Ljava/lang/Object;
invoke-direct {v0}, Ljava/lang/Object;-><init>()V
return-object v0
"""
)
}
} ?: return HideCommentAdsFingerprint.toErrorResult()
return PatchResultSuccess()
}
}

View File

@ -0,0 +1,25 @@
package app.revanced.patches.reddit.ad.general.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object AdPostFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.CONST_STRING,
null,
Opcode.CONST_STRING,
null,
Opcode.INVOKE_DIRECT,
Opcode.IPUT_OBJECT
),
// "children" are present throughout multiple versions
strings = listOf(
"children",
"uxExperiences"
),
customFingerprint = { methodDef, classDef -> methodDef.definingClass.endsWith("/Listing;") && methodDef.name == "<init>" && classDef.sourceFile == "Listing.kt" },
)

View File

@ -0,0 +1,19 @@
package app.revanced.patches.reddit.ad.general.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object NewAdPostFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
),
strings = listOf(
"chain",
"feedElement"
),
customFingerprint = { _, classDef -> classDef.sourceFile == "AdElementConverter.kt" },
)

View File

@ -0,0 +1,94 @@
package app.revanced.patches.reddit.ad.general.patch
import app.revanced.extensions.toErrorResult
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.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
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.fingerprints.AdPostFingerprint
import app.revanced.patches.reddit.ad.general.fingerprints.NewAdPostFingerprint
import app.revanced.patches.reddit.utils.annotations.RedditCompatibility
import app.revanced.patches.reddit.utils.integrations.patch.IntegrationsPatch
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.instruction.formats.Instruction22c
import org.jf.dexlib2.iface.reference.FieldReference
@Patch
@Name("hide-ads")
@Description("Removes ads from the Reddit.")
@DependsOn(
[
HideBannerPatch::class,
HideCommentAdsPatch::class,
IntegrationsPatch::class
]
)
@RedditCompatibility
@Version("0.0.2")
class HideAdsPatch : BytecodePatch(
listOf(
AdPostFingerprint,
NewAdPostFingerprint
)
) {
override fun execute(context: BytecodeContext): PatchResult {
// region Filter promoted ads (does not work in popular or latest feed)
AdPostFingerprint.result?.let {
it.mutableMethod.apply {
val targetIndex = it.scanResult.patternScanResult!!.endIndex
val targetReference = getInstruction<ReferenceInstruction>(targetIndex).reference
val targetReferenceName = (targetReference as FieldReference).name
if (targetReferenceName != "children")
throw PatchResultError("Method signature reference name did not match: $targetReferenceName")
val castedInstruction = getInstruction<Instruction22c>(targetIndex)
removeInstruction(targetIndex)
addInstructions(
targetIndex, """
invoke-static {v${castedInstruction.registerA}}, $FILTER_METHOD_DESCRIPTOR
move-result-object v0
iput-object v0, v${castedInstruction.registerB}, ${castedInstruction.reference}
"""
)
}
} ?: return AdPostFingerprint.toErrorResult()
// 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.
NewAdPostFingerprint.result?.let {
it.mutableMethod.apply {
val targetIndex = it.scanResult.patternScanResult!!.endIndex
val targetParameter =
getInstruction<ReferenceInstruction>(targetIndex).reference.toString()
if (!targetParameter.endsWith("Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z"))
throw PatchResultError("Method signature parameter did not match: $targetParameter")
removeInstruction(targetIndex)
}
} ?: return NewAdPostFingerprint.toErrorResult()
return PatchResultSuccess()
}
private companion object {
private const val FILTER_METHOD_DESCRIPTOR =
"Lapp/revanced/reddit/patches/FilterPromotedLinksPatch;" +
"->filterChildren(Ljava/lang/Iterable;)Ljava/util/List;"
}
}

View File

@ -0,0 +1,10 @@
package app.revanced.patches.reddit.layout.premiumicon.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
object PremiumIconFingerprint : MethodFingerprint(
returnType = "Z",
customFingerprint = { methodDef, classDef ->
methodDef.definingClass.endsWith("MyAccount;") && methodDef.name == "isPremiumSubscriber" && classDef.sourceFile == "MyAccount.kt"
}
)

View File

@ -0,0 +1,39 @@
package app.revanced.patches.reddit.layout.premiumicon.patch
import app.revanced.extensions.toErrorResult
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.Patch
import app.revanced.patches.reddit.layout.premiumicon.fingerprints.PremiumIconFingerprint
import app.revanced.patches.reddit.utils.annotations.RedditCompatibility
@Patch
@Name("premium-icon-reddit")
@Description("Unlocks premium Reddit app icons.")
@RedditCompatibility
@Version("0.0.1")
class PremiumIconPatch : BytecodePatch(
listOf(PremiumIconFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
PremiumIconFingerprint.result?.let {
it.mutableMethod.apply {
addInstructions(
0, """
const/4 v0, 0x1
return v0
"""
)
}
} ?: return PremiumIconFingerprint.toErrorResult()
return PatchResultSuccess()
}
}

View File

@ -0,0 +1,22 @@
package app.revanced.patches.reddit.misc.tracking.url.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object ShareLinkFactoryFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.CONST_STRING,
Opcode.CONST_STRING,
Opcode.INVOKE_DIRECT,
Opcode.APUT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC, // Returns the URL.
Opcode.MOVE_RESULT_OBJECT
),
customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("ShareLinkFactory;") }
)

View File

@ -0,0 +1,52 @@
package app.revanced.patches.reddit.misc.tracking.url.patch
import app.revanced.extensions.toErrorResult
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.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patches.reddit.misc.tracking.url.fingerprints.ShareLinkFactoryFingerprint
import app.revanced.patches.reddit.utils.annotations.RedditCompatibility
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
@Patch
@Name("sanitize-sharing-links")
@Description("Removes (tracking) query parameters from the URLs when sharing links.")
@RedditCompatibility
@Version("0.0.1")
@RequiresIntegrations
class SanitizeUrlQueryPatch : BytecodePatch(
listOf(ShareLinkFactoryFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
ShareLinkFactoryFingerprint.result?.let { result ->
result.mutableMethod.apply {
val insertIndex = result.scanResult.patternScanResult!!.endIndex + 1
val urlRegister = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
addInstructions(
insertIndex,
"""
invoke-static {v$urlRegister}, $SANITIZE_METHOD_DESCRIPTOR
move-result-object v$urlRegister
"""
)
}
} ?: return ShareLinkFactoryFingerprint.toErrorResult()
return PatchResultSuccess()
}
private companion object {
private const val SANITIZE_METHOD_DESCRIPTOR =
"Lapp/revanced/reddit/patches/SanitizeUrlQueryPatch;" +
"->stripQueryParameters(Ljava/lang/String;)Ljava/lang/String;"
}
}

View File

@ -0,0 +1,8 @@
package app.revanced.patches.reddit.utils.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility([Package("com.reddit.frontpage")])
@Target(AnnotationTarget.CLASS)
internal annotation class RedditCompatibility