mirror of
https://github.com/revanced/revanced-patches
synced 2024-12-03 00:42:55 +01:00
feat(twitch/block-embedded-ads): block-embedded-ads
patch support (#1160)
This commit is contained in:
parent
8fe9e46efd
commit
641d33fc7e
@ -25,7 +25,7 @@ internal data class ArrayResource(
|
|||||||
resourceCallback?.invoke(item)
|
resourceCallback?.invoke(item)
|
||||||
|
|
||||||
this.appendChild(ownerDocument.createElement("item").also { itemNode ->
|
this.appendChild(ownerDocument.createElement("item").also { itemNode ->
|
||||||
itemNode.textContent = item.value
|
itemNode.textContent = "@string/${item.name}"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ abstract class AbstractSettingsResourcePatch(
|
|||||||
* @param arrayResource The array resource to add.
|
* @param arrayResource The array resource to add.
|
||||||
*/
|
*/
|
||||||
fun addArray(arrayResource: ArrayResource) =
|
fun addArray(arrayResource: ArrayResource) =
|
||||||
arraysNode!!.addResource(arrayResource)
|
arraysNode!!.addResource(arrayResource) { it.include() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a preference to the settings.
|
* Add a preference to the settings.
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.patches.twitch.ad.embedded.annotations
|
||||||
|
|
||||||
|
import app.revanced.patcher.annotation.Compatibility
|
||||||
|
import app.revanced.patcher.annotation.Package
|
||||||
|
|
||||||
|
@Compatibility([Package("tv.twitch.android.app")])
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class EmbeddedAdsCompatibility
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patches.twitch.ad.embedded.fingerprints
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
|
||||||
|
object CreateUsherClientFingerprint : MethodFingerprint(
|
||||||
|
customFingerprint = { method ->
|
||||||
|
method.definingClass.endsWith("Ltv/twitch/android/network/OkHttpClientFactory;") && method.name == "buildOkHttpClient"
|
||||||
|
}
|
||||||
|
)
|
@ -0,0 +1,78 @@
|
|||||||
|
package app.revanced.patches.twitch.ad.embedded.patch
|
||||||
|
|
||||||
|
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.MethodFingerprintExtensions.name
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
|
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.shared.settings.preference.impl.ArrayResource
|
||||||
|
import app.revanced.patches.shared.settings.preference.impl.ListPreference
|
||||||
|
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
||||||
|
import app.revanced.patches.twitch.ad.embedded.annotations.EmbeddedAdsCompatibility
|
||||||
|
import app.revanced.patches.twitch.ad.embedded.fingerprints.CreateUsherClientFingerprint
|
||||||
|
import app.revanced.patches.twitch.ad.video.patch.VideoAdsPatch
|
||||||
|
import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
|
||||||
|
import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
|
||||||
|
|
||||||
|
@Patch
|
||||||
|
@DependsOn([VideoAdsPatch::class, IntegrationsPatch::class, SettingsPatch::class])
|
||||||
|
@Name("block-embedded-ads")
|
||||||
|
@Description("Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker.")
|
||||||
|
@EmbeddedAdsCompatibility
|
||||||
|
@Version("0.0.1")
|
||||||
|
class EmbeddedAdsPatch : BytecodePatch(
|
||||||
|
listOf(CreateUsherClientFingerprint)
|
||||||
|
) {
|
||||||
|
override fun execute(context: BytecodeContext): PatchResult {
|
||||||
|
val result = CreateUsherClientFingerprint.result ?: return PatchResultError("${CreateUsherClientFingerprint.name} not found")
|
||||||
|
|
||||||
|
// Inject OkHttp3 application interceptor
|
||||||
|
result.mutableMethod.addInstructions(
|
||||||
|
3,
|
||||||
|
"""
|
||||||
|
invoke-static {}, Lapp/revanced/twitch/patches/EmbeddedAdsPatch;->createRequestInterceptor()Lapp/revanced/twitch/api/RequestInterceptor;
|
||||||
|
move-result-object v2
|
||||||
|
invoke-virtual {v0, v2}, Lokhttp3/OkHttpClient${"$"}Builder;->addInterceptor(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient${"$"}Builder;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsPatch.PreferenceScreen.ADS.SURESTREAM.addPreferences(
|
||||||
|
ListPreference(
|
||||||
|
"revanced_block_embedded_ads",
|
||||||
|
StringResource(
|
||||||
|
"revanced_block_embedded_ads",
|
||||||
|
"Block embedded video ads"
|
||||||
|
),
|
||||||
|
ArrayResource(
|
||||||
|
"revanced_hls_proxies",
|
||||||
|
listOf(
|
||||||
|
StringResource("revanced_proxy_disabled", "Disabled"),
|
||||||
|
StringResource("revanced_proxy_ttv_lol", "TTV LOL proxy"),
|
||||||
|
StringResource("revanced_proxy_purpleadblock", "PurpleAdBlock proxy"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ArrayResource(
|
||||||
|
"revanced_hls_proxies_values",
|
||||||
|
listOf(
|
||||||
|
StringResource("key_revanced_proxy_disabled", "disabled"),
|
||||||
|
StringResource("key_revanced_proxy_ttv_lol", "ttv-lol"),
|
||||||
|
StringResource("key_revanced_proxy_purpleadblock", "purpleadblock")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"ttv-lol"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsPatch.addString("revanced_embedded_ads_service_unavailable", "%s is unavailable. Ads may show. Try switching to another ad block service in settings.")
|
||||||
|
SettingsPatch.addString("revanced_embedded_ads_service_failed", "%s server returned an error. Ads may show. Try switching to another ad block service in settings.")
|
||||||
|
|
||||||
|
return PatchResultSuccess()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package app.revanced.patches.twitch.ad.shared.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.instruction
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
|
|
||||||
|
abstract class AbstractAdPatch(
|
||||||
|
val conditionCall: String,
|
||||||
|
val skipLabelName: String,
|
||||||
|
internal val fingerprints: Iterable<MethodFingerprint>? = null,
|
||||||
|
) : BytecodePatch(fingerprints) {
|
||||||
|
|
||||||
|
protected fun createConditionInstructions(register: String = "v0") = """
|
||||||
|
invoke-static { }, $conditionCall
|
||||||
|
move-result $register
|
||||||
|
if-eqz $register, :$skipLabelName
|
||||||
|
"""
|
||||||
|
|
||||||
|
protected data class ReturnMethod(val returnType: Char = 'V', val value: String = "")
|
||||||
|
|
||||||
|
protected fun BytecodeContext.blockMethods(clazz: String, vararg methodNames: String, returnMethod: ReturnMethod = ReturnMethod()): Boolean {
|
||||||
|
|
||||||
|
return with(findClass(clazz)?.mutableClass) {
|
||||||
|
this ?: return false
|
||||||
|
|
||||||
|
this.methods.filter { methodNames.contains(it.name) }.forEach {
|
||||||
|
val retIntructions = when(returnMethod.returnType) {
|
||||||
|
'V' -> "return-void"
|
||||||
|
'Z' -> """
|
||||||
|
const/4 v0, ${returnMethod.value}
|
||||||
|
return v0
|
||||||
|
"""
|
||||||
|
else -> throw NotImplementedError()
|
||||||
|
}
|
||||||
|
it.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
${createConditionInstructions("v0")}
|
||||||
|
$retIntructions
|
||||||
|
""",
|
||||||
|
listOf(ExternalLabel(skipLabelName, it.instruction(0)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
package app.revanced.patches.twitch.ad.video.fingerprints
|
package app.revanced.patches.twitch.ad.video.fingerprints
|
||||||
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
|
||||||
object AdsManagerFingerprint : MethodFingerprint(
|
object GetReadyToShowAdFingerprint : MethodFingerprint(
|
||||||
customFingerprint = { method ->
|
customFingerprint = { method ->
|
||||||
method.definingClass.endsWith("AdsManagerImpl;") && method.name == "playAds"
|
method.definingClass.endsWith("/StreamDisplayAdsPresenter;") && method.name == "getReadyToShowAdOrAbort"
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -6,7 +6,6 @@ import app.revanced.patcher.annotation.Version
|
|||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.instruction
|
import app.revanced.patcher.extensions.instruction
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
|
||||||
import app.revanced.patcher.patch.PatchResult
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
import app.revanced.patcher.patch.annotations.DependsOn
|
import app.revanced.patcher.patch.annotations.DependsOn
|
||||||
@ -14,10 +13,11 @@ import app.revanced.patcher.patch.annotations.Patch
|
|||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
||||||
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
|
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
|
||||||
|
import app.revanced.patches.twitch.ad.shared.util.AbstractAdPatch
|
||||||
import app.revanced.patches.twitch.ad.video.annotations.VideoAdsCompatibility
|
import app.revanced.patches.twitch.ad.video.annotations.VideoAdsCompatibility
|
||||||
import app.revanced.patches.twitch.ad.video.fingerprints.AdsManagerFingerprint
|
|
||||||
import app.revanced.patches.twitch.ad.video.fingerprints.CheckAdEligibilityLambdaFingerprint
|
import app.revanced.patches.twitch.ad.video.fingerprints.CheckAdEligibilityLambdaFingerprint
|
||||||
import app.revanced.patches.twitch.ad.video.fingerprints.ContentConfigShowAdsFingerprint
|
import app.revanced.patches.twitch.ad.video.fingerprints.ContentConfigShowAdsFingerprint
|
||||||
|
import app.revanced.patches.twitch.ad.video.fingerprints.GetReadyToShowAdFingerprint
|
||||||
import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
|
import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
|
||||||
import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
|
import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
|
||||||
|
|
||||||
@ -27,20 +27,63 @@ import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
|
|||||||
@Description("Blocks video ads in streams and VODs.")
|
@Description("Blocks video ads in streams and VODs.")
|
||||||
@VideoAdsCompatibility
|
@VideoAdsCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class VideoAdsPatch : BytecodePatch(
|
class VideoAdsPatch : AbstractAdPatch(
|
||||||
|
"Lapp/revanced/twitch/patches/VideoAdsPatch;->shouldBlockVideoAds()Z",
|
||||||
|
"show_video_ads",
|
||||||
listOf(
|
listOf(
|
||||||
ContentConfigShowAdsFingerprint,
|
ContentConfigShowAdsFingerprint,
|
||||||
AdsManagerFingerprint,
|
CheckAdEligibilityLambdaFingerprint,
|
||||||
CheckAdEligibilityLambdaFingerprint
|
GetReadyToShowAdFingerprint
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
private fun createConditionInstructions(register: String = "v0") = """
|
|
||||||
invoke-static { }, Lapp/revanced/twitch/patches/VideoAdsPatch;->shouldBlockVideoAds()Z
|
|
||||||
move-result $register
|
|
||||||
if-eqz $register, :show_video_ads
|
|
||||||
"""
|
|
||||||
|
|
||||||
override fun execute(context: BytecodeContext): PatchResult {
|
override fun execute(context: BytecodeContext): PatchResult {
|
||||||
|
/* Amazon ads SDK */
|
||||||
|
context.blockMethods(
|
||||||
|
"Lcom/amazon/ads/video/player/AdsManagerImpl;",
|
||||||
|
"playAds"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Twitch ads manager */
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/shared/ads/VideoAdManager;",
|
||||||
|
"checkAdEligibilityAndRequestAd", "requestAd", "requestAds"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Various ad presenters */
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/shared/ads/AdsPlayerPresenter;",
|
||||||
|
"requestAd", "requestFirstAd", "requestFirstAdIfEligible", "requestMidroll", "requestAdFromMultiAdFormatEvent"
|
||||||
|
)
|
||||||
|
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/shared/ads/AdsVodPlayerPresenter;",
|
||||||
|
"requestAd", "requestFirstAd",
|
||||||
|
)
|
||||||
|
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/feature/theatre/ads/AdEdgeAllocationPresenter;",
|
||||||
|
"parseAdAndCheckEligibility", "requestAdsAfterEligibilityCheck", "showAd", "bindMultiAdFormatAllocation"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* A/B ad testing experiments */
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/provider/experiments/helpers/DisplayAdsExperimentHelper;",
|
||||||
|
"areDisplayAdsEnabled",
|
||||||
|
returnMethod = ReturnMethod('Z', "0")
|
||||||
|
)
|
||||||
|
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/shared/ads/tracking/MultiFormatAdsTrackingExperiment;",
|
||||||
|
"shouldUseMultiAdFormatTracker", "shouldUseVideoAdTracker",
|
||||||
|
returnMethod = ReturnMethod('Z', "0")
|
||||||
|
)
|
||||||
|
|
||||||
|
context.blockMethods(
|
||||||
|
"Ltv/twitch/android/shared/ads/MultiformatAdsExperiment;",
|
||||||
|
"shouldDisableClientSideLivePreroll", "shouldDisableClientSideVodPreroll",
|
||||||
|
returnMethod = ReturnMethod('Z', "1")
|
||||||
|
)
|
||||||
|
|
||||||
// Pretend our player is ineligible for all ads
|
// Pretend our player is ineligible for all ads
|
||||||
with(CheckAdEligibilityLambdaFingerprint.result!!) {
|
with(CheckAdEligibilityLambdaFingerprint.result!!) {
|
||||||
mutableMethod.addInstructions(
|
mutableMethod.addInstructions(
|
||||||
@ -52,7 +95,22 @@ class VideoAdsPatch : BytecodePatch(
|
|||||||
move-result-object p0
|
move-result-object p0
|
||||||
return-object p0
|
return-object p0
|
||||||
""",
|
""",
|
||||||
listOf(ExternalLabel("show_video_ads", mutableMethod.instruction(0)))
|
listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(GetReadyToShowAdFingerprint.result!!) {
|
||||||
|
val adFormatDeclined = "Ltv/twitch/android/shared/display/ads/theatre/StreamDisplayAdsPresenter\$Action\$AdFormatDeclined;"
|
||||||
|
mutableMethod.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
${createConditionInstructions()}
|
||||||
|
sget-object p2, $adFormatDeclined->INSTANCE:$adFormatDeclined
|
||||||
|
invoke-static {p1, p2}, Ltv/twitch/android/core/mvp/presenter/StateMachineKt;->plus(Ltv/twitch/android/core/mvp/presenter/PresenterState;Ltv/twitch/android/core/mvp/presenter/PresenterAction;)Ltv/twitch/android/core/mvp/presenter/StateAndAction;
|
||||||
|
move-result-object p1
|
||||||
|
return-object p1
|
||||||
|
""",
|
||||||
|
listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,24 +119,12 @@ class VideoAdsPatch : BytecodePatch(
|
|||||||
mutableMethod.addInstructions(0, """
|
mutableMethod.addInstructions(0, """
|
||||||
${createConditionInstructions()}
|
${createConditionInstructions()}
|
||||||
const/4 v0, 0
|
const/4 v0, 0
|
||||||
:show_video_ads
|
:$skipLabelName
|
||||||
return v0
|
return v0
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block playAds call
|
|
||||||
with(AdsManagerFingerprint.result!!) {
|
|
||||||
mutableMethod.addInstructions(
|
|
||||||
0,
|
|
||||||
"""
|
|
||||||
${createConditionInstructions()}
|
|
||||||
return-void
|
|
||||||
""",
|
|
||||||
listOf(ExternalLabel("show_video_ads", mutableMethod.instruction(0)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences(
|
SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences(
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
"revanced_block_video_ads",
|
"revanced_block_video_ads",
|
||||||
|
@ -173,6 +173,7 @@ class SettingsPatch : BytecodePatch(
|
|||||||
val GENERAL = CustomCategory("general", "General settings")
|
val GENERAL = CustomCategory("general", "General settings")
|
||||||
val OTHER = CustomCategory("other", "Other settings")
|
val OTHER = CustomCategory("other", "Other settings")
|
||||||
val CLIENT_SIDE = CustomCategory("client_ads", "Client-side ads")
|
val CLIENT_SIDE = CustomCategory("client_ads", "Client-side ads")
|
||||||
|
val SURESTREAM = CustomCategory("surestream_ads", "Server-side surestream ads")
|
||||||
|
|
||||||
internal inner class CustomCategory(key: String, title: String) : Screen.Category(key, title) {
|
internal inner class CustomCategory(key: String, title: String) : Screen.Category(key, title) {
|
||||||
/* For Twitch, we need to load our CustomPreferenceCategory class instead of the default one. */
|
/* For Twitch, we need to load our CustomPreferenceCategory class instead of the default one. */
|
||||||
|
Loading…
Reference in New Issue
Block a user