feat(twitch/block-embedded-ads): block-embedded-ads
patch (#1166)
Co-authored-by: Tim Schneeberger <tim.schneeberger@outlook.de>
This commit is contained in:
parent
b327619069
commit
018606e983
|
@ -25,7 +25,7 @@ internal data class ArrayResource(
|
|||
resourceCallback?.invoke(item)
|
||||
|
||||
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.
|
||||
*/
|
||||
fun addArray(arrayResource: ArrayResource) =
|
||||
arraysNode!!.addResource(arrayResource)
|
||||
arraysNode!!.addResource(arrayResource) { it.include() }
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
object AdsManagerFingerprint : MethodFingerprint(
|
||||
object GetReadyToShowAdFingerprint : MethodFingerprint(
|
||||
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.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.instruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
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.patches.shared.settings.preference.impl.StringResource
|
||||
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.fingerprints.AdsManagerFingerprint
|
||||
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.GetReadyToShowAdFingerprint
|
||||
import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
|
||||
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.")
|
||||
@VideoAdsCompatibility
|
||||
@Version("0.0.1")
|
||||
class VideoAdsPatch : BytecodePatch(
|
||||
class VideoAdsPatch : AbstractAdPatch(
|
||||
"Lapp/revanced/twitch/patches/VideoAdsPatch;->shouldBlockVideoAds()Z",
|
||||
"show_video_ads",
|
||||
listOf(
|
||||
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 {
|
||||
/* 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
|
||||
with(CheckAdEligibilityLambdaFingerprint.result!!) {
|
||||
mutableMethod.addInstructions(
|
||||
|
@ -52,7 +95,22 @@ class VideoAdsPatch : BytecodePatch(
|
|||
move-result-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, """
|
||||
${createConditionInstructions()}
|
||||
const/4 v0, 0
|
||||
:show_video_ads
|
||||
:$skipLabelName
|
||||
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(
|
||||
SwitchPreference(
|
||||
"revanced_block_video_ads",
|
||||
|
|
|
@ -173,6 +173,7 @@ class SettingsPatch : BytecodePatch(
|
|||
val GENERAL = CustomCategory("general", "General settings")
|
||||
val OTHER = CustomCategory("other", "Other settings")
|
||||
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) {
|
||||
/* For Twitch, we need to load our CustomPreferenceCategory class instead of the default one. */
|
||||
|
|
Loading…
Reference in a new issue