From 5c8fa2c2bf8c9bbfbfa1d4afaddfd165c30bda91 Mon Sep 17 00:00:00 2001 From: Tim Schneeberger Date: Tue, 29 Nov 2022 00:20:58 +0100 Subject: [PATCH] feat(twitch): settings for patches (#1142) --- .../twitch/ad/audio/patch/AudioAdsPatch.kt | 40 ++++++++++- .../twitch/ad/video/patch/VideoAdsPatch.kt | 51 +++++++++++-- .../ChatUtilCreateDeletedSpanFingerprint.kt | 9 +++ .../patch/ShowDeletedMessagesPatch.kt | 72 +++++++++++++++++-- .../twitch/debug/patch/DebugModePatch.kt | 29 +++++++- 5 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt index ac680f5af..a5cccb1c7 100644 --- a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt @@ -4,15 +4,23 @@ 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.addInstruction +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 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.audio.annotations.AudioAdsCompatibility import app.revanced.patches.twitch.ad.audio.fingerprints.AudioAdsPresenterPlayFingerprint +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch @Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) @Name("block-audio-ads") @Description("Blocks audio ads in streams and VODs.") @AudioAdsCompatibility @@ -23,9 +31,37 @@ class AudioAdsPatch : BytecodePatch( override fun execute(context: BytecodeContext): PatchResult { // Block playAds call with(AudioAdsPresenterPlayFingerprint.result!!) { - mutableMethod.addInstruction(0, "return-void") + mutableMethod.addInstructions( + 0, + """ + invoke-static { }, Lapp/revanced/twitch/patches/AudioAdsPatch;->shouldBlockAudioAds()Z + move-result v0 + if-eqz v0, :show_audio_ads + return-void + """, + listOf(ExternalLabel("show_audio_ads", mutableMethod.instruction(0))) + ) } + SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences( + SwitchPreference( + "revanced_block_audio_ads", + StringResource( + "revanced_block_audio_ads", + "Block audio ads" + ), + true, + StringResource( + "revanced_block_audio_ads_on", + "Audio ads are blocked" + ), + StringResource( + "revanced_block_audio_ads_off", + "Audio ads are unblocked" + ), + ) + ) + return PatchResultSuccess() } } diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt index e2ae8440e..5f2acf66d 100644 --- a/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt @@ -4,18 +4,25 @@ 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.addInstruction 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 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.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.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch @Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) @Name("block-video-ads") @Description("Blocks video ads in streams and VODs.") @VideoAdsCompatibility @@ -27,24 +34,34 @@ class VideoAdsPatch : BytecodePatch( CheckAdEligibilityLambdaFingerprint ) ) { + 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 { // Pretend our player is ineligible for all ads with(CheckAdEligibilityLambdaFingerprint.result!!) { mutableMethod.addInstructions( 0, """ - const/4 v0, 0 + ${createConditionInstructions()} + const/4 v0, 0 invoke-static {v0}, Lio/reactivex/Single;->just(Ljava/lang/Object;)Lio/reactivex/Single; move-result-object p0 return-object p0 - """ + """, + listOf(ExternalLabel("show_video_ads", mutableMethod.instruction(0))) ) } // Spoof showAds JSON field with(ContentConfigShowAdsFingerprint.result!!) { mutableMethod.addInstructions(0, """ + ${createConditionInstructions()} const/4 v0, 0 + :show_video_ads return v0 """ ) @@ -52,9 +69,35 @@ class VideoAdsPatch : BytecodePatch( // Block playAds call with(AdsManagerFingerprint.result!!) { - mutableMethod.addInstruction(0, "return-void") + 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", + StringResource( + "revanced_block_video_ads", + "Block video ads" + ), + true, + StringResource( + "revanced_block_video_ads_on", + "Video ads are blocked" + ), + StringResource( + "revanced_block_video_ads_off", + "Video ads are unblocked" + ), + ) + ) + return PatchResultSuccess() } } diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt new file mode 100644 index 000000000..733710b2b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.chat.antidelete.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ChatUtilCreateDeletedSpanFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ChatUtil\$Companion;") && methodDef.name == "createDeletedSpanFromChatMessageSpan" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt index a6f3bfa8a..dba6e833e 100644 --- a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt @@ -6,13 +6,19 @@ import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.* import app.revanced.patcher.patch.* +import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.smali.ExternalLabel +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.chat.antidelete.annotations.ShowDeletedMessagesCompatibility import app.revanced.patches.twitch.chat.antidelete.fingerprints.* -import org.jf.dexlib2.Opcode -import org.jf.dexlib2.builder.instruction.BuilderInstruction10x +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch @Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) @Name("show-deleted-messages") @Description("Shows deleted chat messages behind a clickable spoiler.") @ShowDeletedMessagesCompatibility @@ -21,25 +27,77 @@ class ShowDeletedMessagesPatch : BytecodePatch( listOf( SetHasModAccessFingerprint, DeletedMessageClickableSpanCtorFingerprint, + ChatUtilCreateDeletedSpanFingerprint ) ) { + private fun createSpoilerConditionInstructions(register: String = "v0") = """ + invoke-static {}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->shouldUseSpoiler()Z + move-result $register + if-eqz $register, :no_spoiler + """ + override fun execute(context: BytecodeContext): PatchResult { - // Force set hasModAccess member to true in constructor + // Spoiler mode: Force set hasModAccess member to true in constructor with(DeletedMessageClickableSpanCtorFingerprint.result!!.mutableMethod) { addInstructions( implementation!!.instructions.lastIndex, /* place in front of return-void */ """ + ${createSpoilerConditionInstructions()} const/4 v0, 1 iput-boolean v0, p0, $definingClass->hasModAccess:Z - """ + """, + listOf(ExternalLabel("no_spoiler", instruction(implementation!!.instructions.lastIndex))) ) } - // Disable setHasModAccess setter - with(SetHasModAccessFingerprint.result!!.mutableMethod.implementation!!) { - addInstruction(0, BuilderInstruction10x(Opcode.RETURN_VOID)) + // Spoiler mode: Disable setHasModAccess setter + with(SetHasModAccessFingerprint.result!!) { + mutableMethod.addInstruction(0, "return-void") } + // Cross-out mode: Reformat span of deleted message + with(ChatUtilCreateDeletedSpanFingerprint.result!!) { + mutableMethod.addInstructions( + 0, + """ + invoke-static {p2}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->reformatDeletedMessage(Landroid/text/Spanned;)Landroid/text/Spanned; + move-result-object v0 + if-eqz v0, :no_reformat + return-object v0 + """, + listOf(ExternalLabel("no_reformat", mutableMethod.instruction(0))) + ) + } + + SettingsPatch.PreferenceScreen.CHAT.GENERAL.addPreferences( + ListPreference( + "revanced_show_deleted_messages", + StringResource( + "revanced_show_deleted_messages_title", + "Show deleted messages" + ), + ArrayResource( + "revanced_deleted_messages", + listOf( + StringResource("revanced_deleted_messages_hide", "Do not show deleted messages"), + StringResource("revanced_deleted_messages_spoiler", "Hide deleted messages behind a spoiler"), + StringResource("revanced_deleted_messages_cross_out", "Show deleted messages as crossed-out text") + ) + ), + ArrayResource( + "revanced_deleted_messages_values", + listOf( + StringResource("key_revanced_deleted_messages_hide", "hide"), + StringResource("key_revanced_deleted_messages_spoiler", "spoiler"), + StringResource("key_revanced_deleted_messages_cross_out", "cross-out") + ) + ), + "cross-out" + ) + ) + + SettingsPatch.addString("revanced_deleted_msg", "message deleted") + return PatchResultSuccess() } } diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt index 2829b97fd..755efe696 100644 --- a/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt @@ -8,13 +8,19 @@ import app.revanced.patcher.extensions.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.DependsOn import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference import app.revanced.patches.twitch.debug.annotations.DebugModeCompatibility import app.revanced.patches.twitch.debug.fingerprints.IsDebugConfigEnabledFingerprint import app.revanced.patches.twitch.debug.fingerprints.IsOmVerificationEnabledFingerprint import app.revanced.patches.twitch.debug.fingerprints.ShouldShowDebugOptionsFingerprint +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch @Patch(false) +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) @Name("debug-mode") @Description("Enables Twitch's internal debugging mode.") @DebugModeCompatibility @@ -37,13 +43,34 @@ class DebugModePatch : BytecodePatch( addInstructions( 0, """ - const/4 v0, 0x1 + invoke-static {}, Lapp/revanced/twitch/patches/DebugModePatch;->isDebugModeEnabled()Z + move-result v0 return v0 """ ) } } } + + SettingsPatch.PreferenceScreen.MISC.OTHER.addPreferences( + SwitchPreference( + "revanced_debug_mode", + StringResource( + "revanced_debug_mode_enable", + "Enable debug mode" + ), + false, + StringResource( + "revanced_debug_mode_on", + "Debug mode is enabled (not recommended)" + ), + StringResource( + "revanced_debug_mode_off", + "Debug mode is disabled" + ), + ) + ) + return PatchResultSuccess() } }