chore: Merge branch dev to main (#3053)

This commit is contained in:
oSumAtrIX 2024-05-21 02:45:40 +02:00 committed by GitHub
commit f56e2128e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 1168 additions and 134 deletions

View file

@ -1,3 +1,177 @@
# [4.8.0-dev.24](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.23...v4.8.0-dev.24) (2024-05-21)
### Bug Fixes
* **YouTube - Client spoof:** Spoof client to fix playback ([#3199](https://github.com/ReVanced/revanced-patches/issues/3199)) ([bec1eef](https://github.com/ReVanced/revanced-patches/commit/bec1eef10f2eb4e15696acb271357f1621543de1))
# [4.8.0-dev.23](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.22...v4.8.0-dev.23) (2024-05-18)
### Features
* **Piccoma:** Add `Spoof Android device ID` patch ([#3145](https://github.com/ReVanced/revanced-patches/issues/3145)) ([d953c6b](https://github.com/ReVanced/revanced-patches/commit/d953c6bdd4315d2ba44845fd569a3d12ac4d1af0))
# [4.8.0-dev.22](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.21...v4.8.0-dev.22) (2024-05-18)
### Bug Fixes
* Use correct preference key ([3732b2c](https://github.com/ReVanced/revanced-patches/commit/3732b2ce6b617b4c1c6647397b614f8a040eece3))
# [4.8.0-dev.21](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.20...v4.8.0-dev.21) (2024-05-18)
### Features
* **Piccoma:** Add `Disable tracking` patch ([#3143](https://github.com/ReVanced/revanced-patches/issues/3143)) ([8ab9e8f](https://github.com/ReVanced/revanced-patches/commit/8ab9e8f89d2bd014138e31dab7004f8ba77cae10))
* **YouTube - Navigation buttons:** Add option to hide navigation button labels ([#3189](https://github.com/ReVanced/revanced-patches/issues/3189)) ([f9dc705](https://github.com/ReVanced/revanced-patches/commit/f9dc7050513b9fdb7766838a63a172f1478296f7))
# [4.8.0-dev.20](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.19...v4.8.0-dev.20) (2024-05-16)
### Features
* **YT Music:** Add support for `7.01.52` ([#3177](https://github.com/ReVanced/revanced-patches/issues/3177)) ([e9bfb25](https://github.com/ReVanced/revanced-patches/commit/e9bfb25dfe85754fd7fa5c9db934bb4fc52e4694))
# [4.8.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.18...v4.8.0-dev.19) (2024-05-16)
### Features
* **YouTube - Hide Shorts components:** Hide 'Buy super thanks' button ([#3176](https://github.com/ReVanced/revanced-patches/issues/3176)) ([89c1548](https://github.com/ReVanced/revanced-patches/commit/89c154861c8b3afa665542e97ff201c3e84410b2))
# [4.8.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.17...v4.8.0-dev.18) (2024-05-14)
### Bug Fixes
* **YouTube Music:** Make `Hide 'Get Music Premium' label` and `Remove upgrade button` compatible with latest version ([#3164](https://github.com/ReVanced/revanced-patches/issues/3164)) ([3ff20de](https://github.com/ReVanced/revanced-patches/commit/3ff20dee4aea49ca77dcd3fbe148287b55a2b5e3))
# [4.8.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.16...v4.8.0-dev.17) (2024-05-12)
### Features
* **Photomath:** Support version `8.37.0` ([#3109](https://github.com/ReVanced/revanced-patches/issues/3109)) ([fb02b48](https://github.com/ReVanced/revanced-patches/commit/fb02b481e2be8c2bc4441dc5b3dc6a9df3a2a379))
# [4.8.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.15...v4.8.0-dev.16) (2024-05-12)
### Features
* **WarnWetter - Promo code unlock:** Constrain to last working version ([#3110](https://github.com/ReVanced/revanced-patches/issues/3110)) ([92fc8aa](https://github.com/ReVanced/revanced-patches/commit/92fc8aaad80f8fad35b75e6de032692986211536))
# [4.8.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.14...v4.8.0-dev.15) (2024-05-11)
### Bug Fixes
* **YouTube - Restore old video quality menu:** Show advanced quality menu in Shorts quality flyout ([#3155](https://github.com/ReVanced/revanced-patches/issues/3155)) ([c2b5bb7](https://github.com/ReVanced/revanced-patches/commit/c2b5bb723416e43a920817f97b9e0ee4ceab4f6b))
# [4.8.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.13...v4.8.0-dev.14) (2024-05-08)
### Bug Fixes
* **YouTube - SponsorBlock:** Show correct segment times if video is over 24 hours in length ([#3138](https://github.com/ReVanced/revanced-patches/issues/3138)) ([6cdf697](https://github.com/ReVanced/revanced-patches/commit/6cdf697e8e47f6d53964497703dbe79fab3b1821))
# [4.8.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.12...v4.8.0-dev.13) (2024-05-06)
### Bug Fixes
* **YouTube - Navigation buttons:** Adjust summary text of switch notification button ([#3130](https://github.com/ReVanced/revanced-patches/issues/3130)) ([cc8b4c9](https://github.com/ReVanced/revanced-patches/commit/cc8b4c913ed25d07fd4000cfd6318bb06a9d27c0))
# [4.8.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.11...v4.8.0-dev.12) (2024-05-06)
### Bug Fixes
* **YouTube - Player flyout menu:** Remove obsolete `Hide report menu` ([d627d44](https://github.com/ReVanced/revanced-patches/commit/d627d44ad07fa32bb2f247ce24a3591ec5e1be0e))
# [4.8.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.10...v4.8.0-dev.11) (2024-05-06)
### Bug Fixes
* **Reddit is Fun - Spoof client:** Fix login by updating the authorization subdomain from "old" to "ssl" ([b156cb1](https://github.com/ReVanced/revanced-patches/commit/b156cb1d8996c4314d59e3441c6b85d8f704cdff))
# [4.8.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.9...v4.8.0-dev.10) (2024-05-02)
### Features
* **Tumblr:** Add `Disable Ad-Free Banner` patch ([#3091](https://github.com/ReVanced/revanced-patches/issues/3091)) ([54baf08](https://github.com/ReVanced/revanced-patches/commit/54baf08f777b7c975fa0b6508f0a4de19ac491f4))
# [4.8.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.8...v4.8.0-dev.9) (2024-04-29)
### Bug Fixes
* Correctly handle patches jar path if it contains exclamation marks ([056e2d7](https://github.com/ReVanced/revanced-patches/commit/056e2d7dd5bbacb7dc6b109b3e2d44d55e7eb7d3))
# [4.8.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.7...v4.8.0-dev.8) (2024-04-23)
### Bug Fixes
* **YouTube - Hide ads:** Fix string typo ([ecc56d6](https://github.com/ReVanced/revanced-patches/commit/ecc56d643a0c4e5f25b933431f097a03d4bf2e69))
# [4.8.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.6...v4.8.0-dev.7) (2024-04-23)
### Bug Fixes
* URL decode path to JAR containing spaces to get JAR manifest ([#3079](https://github.com/ReVanced/revanced-patches/issues/3079)) ([e1bbcb3](https://github.com/ReVanced/revanced-patches/commit/e1bbcb338dd7fce895b606440bd6f040d5486a64))
# [4.8.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.5...v4.8.0-dev.6) (2024-04-23)
### Features
* **YouTube - Hide ads:** Add option to hide the 'Visit store' button on channel pages ([#3077](https://github.com/ReVanced/revanced-patches/issues/3077)) ([03d2cfa](https://github.com/ReVanced/revanced-patches/commit/03d2cfafbf977340456598a848858ac9452c853f))
# [4.8.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.4...v4.8.0-dev.5) (2024-04-23)
### Bug Fixes
* **YouTube - Hide Shorts components:** Rename option title to make it consistent ([4d6e34b](https://github.com/ReVanced/revanced-patches/commit/4d6e34b0540a3334bd77b2b48a1a5e10329171c8))
# [4.8.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.3...v4.8.0-dev.4) (2024-04-23)
### Features
* **YouTube - Comments:** Add option to hide timestamp and emoji buttons ([#3076](https://github.com/ReVanced/revanced-patches/issues/3076)) ([7efe5ae](https://github.com/ReVanced/revanced-patches/commit/7efe5aefb252a2ed908907ff218b879e2ad1a331))
# [4.8.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.2...v4.8.0-dev.3) (2024-04-22)
### Bug Fixes
* Publicize abstract property ([b7c108e](https://github.com/ReVanced/revanced-patches/commit/b7c108ee201c84df31b079f3fecb6cc2f5eaf9f1))
# [4.8.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.8.0-dev.1...v4.8.0-dev.2) (2024-04-21)
### Bug Fixes
* Case patch option title correctly ([259c8b4](https://github.com/ReVanced/revanced-patches/commit/259c8b4e58df51d92d7e19417e13afa3848afc73))
# [4.8.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.7.0...v4.8.0-dev.1) (2024-04-21)
### Bug Fixes
* **YouTube - Hide video action buttons:** Remove obsolete `hide Shop button` ([#3057](https://github.com/ReVanced/revanced-patches/issues/3057)) ([b5e34f3](https://github.com/ReVanced/revanced-patches/commit/b5e34f3aabc1d9df8c41f92251618243caecdc9f))
### Features
* **YouTube - Hide Shorts components:** Hide like / dislike button in video ads ([#3062](https://github.com/ReVanced/revanced-patches/issues/3062)) ([1296985](https://github.com/ReVanced/revanced-patches/commit/12969853adfe530eb6006df38e1a5aa30b28fdf9))
# [4.7.0](https://github.com/ReVanced/revanced-patches/compare/v4.6.0...v4.7.0) (2024-04-21) # [4.7.0](https://github.com/ReVanced/revanced-patches/compare/v4.6.0...v4.7.0) (2024-04-21)

View file

@ -64,7 +64,7 @@ This document describes how to contribute to ReVanced Patches.
## 📖 Resources to help you get started ## 📖 Resources to help you get started
* The [documentation](https://github.com/ReVanced/revanced-patcher/tree/docs/docs) contains the fundamentals * The [documentation](https://github.com/ReVanced/revanced-patcher/tree/main/docs) contains the fundamentals
of ReVanced Patcher and how to use ReVanced Patcher to create patches of ReVanced Patcher and how to use ReVanced Patcher to create patches
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on * [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
* [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests * [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests

View file

@ -30,6 +30,7 @@ public final class app/revanced/patches/all/misc/debugging/EnableAndroidDebuggin
public final class app/revanced/patches/all/misc/hex/HexPatch : app/revanced/patches/shared/misc/hex/BaseHexPatch { public final class app/revanced/patches/all/misc/hex/HexPatch : app/revanced/patches/shared/misc/hex/BaseHexPatch {
public fun <init> ()V public fun <init> ()V
public fun getReplacements ()Ljava/util/List;
} }
public final class app/revanced/patches/all/misc/network/OverrideCertificatePinningPatch : app/revanced/patcher/patch/ResourcePatch { public final class app/revanced/patches/all/misc/network/OverrideCertificatePinningPatch : app/revanced/patcher/patch/ResourcePatch {
@ -486,6 +487,18 @@ public final class app/revanced/patches/photomath/misc/unlock/plus/UnlockPlusPat
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/piccomafr/misc/SpoofAndroidDeviceIdPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/piccomafr/misc/SpoofAndroidDeviceIdPatch;
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/piccomafr/tracking/DisableTrackingPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/piccomafr/tracking/DisableTrackingPatch;
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/pixiv/ads/HideAdsPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/pixiv/ads/HideAdsPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/pixiv/ads/HideAdsPatch; public static final field INSTANCE Lapp/revanced/patches/pixiv/ads/HideAdsPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
@ -575,6 +588,7 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/detec
public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch { public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatch; public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatch;
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
public fun patchMiscellaneous (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
} }
@ -677,6 +691,7 @@ public abstract class app/revanced/patches/shared/misc/hex/BaseHexPatch : app/re
public fun <init> ()V public fun <init> ()V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
public abstract fun getReplacements ()Ljava/util/List;
} }
public final class app/revanced/patches/shared/misc/hex/BaseHexPatch$Replacement { public final class app/revanced/patches/shared/misc/hex/BaseHexPatch$Replacement {
@ -1060,6 +1075,12 @@ public final class app/revanced/patches/tumblr/ads/DisableDashboardAds : app/rev
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/tumblr/annoyances/adfree/DisableAdFreeBannerPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/tumblr/annoyances/adfree/DisableAdFreeBannerPatch;
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/tumblr/annoyances/inappupdate/DisableInAppUpdatePatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/tumblr/annoyances/inappupdate/DisableInAppUpdatePatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/tumblr/annoyances/inappupdate/DisableInAppUpdatePatch; public static final field INSTANCE Lapp/revanced/patches/tumblr/annoyances/inappupdate/DisableInAppUpdatePatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
@ -1610,12 +1631,10 @@ public final class app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDevic
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/ClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch { public final class app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/ClientSpoofPatch; public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Triple; public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch : app/revanced/patcher/patch/BytecodePatch {
@ -1630,6 +1649,14 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Triple;
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
}
public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch { public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch; public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch;
} }

View file

@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 4.7.0 version = 4.8.0-dev.24

View file

@ -19,7 +19,7 @@ class HexPatch : BaseHexPatch() {
// Replace the custom option type with a stringArrayOption once the issue is resolved. // Replace the custom option type with a stringArrayOption once the issue is resolved.
private val replacementsOption by registerNewPatchOption<PatchClass<*>, List<String>>( private val replacementsOption by registerNewPatchOption<PatchClass<*>, List<String>>(
key = "replacements", key = "replacements",
title = "replacements", title = "Replacements",
description = """ description = """
Hexadecimal patterns to search for and replace with another in a target file. Hexadecimal patterns to search for and replace with another in a target file.

View file

@ -12,8 +12,6 @@ internal object HideGetPremiumFingerprint : MethodFingerprint(
listOf( listOf(
Opcode.IF_NEZ, Opcode.IF_NEZ,
Opcode.CONST_16, Opcode.CONST_16,
Opcode.GOTO,
Opcode.NOP,
Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL,
), ),
listOf("FEmusic_history", "FEmusic_offline"), listOf("FEmusic_history", "FEmusic_offline"),

View file

@ -13,7 +13,6 @@ internal object PivotBarConstructorFingerprint : MethodFingerprint(
Opcode.CHECK_CAST, Opcode.CHECK_CAST,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.GOTO, Opcode.GOTO,
Opcode.NOP,
Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT,
Opcode.RETURN_VOID, Opcode.RETURN_VOID,
), ),

View file

@ -1,19 +1,10 @@
package app.revanced.patches.music.misc.integrations.fingerprints package app.revanced.patches.music.misc.integrations.fingerprints
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object ApplicationInitFingerprint : IntegrationsFingerprint( internal object ApplicationInitFingerprint : IntegrationsFingerprint(
returnType = "V", returnType = "V",
parameters = emptyList(), parameters = emptyList(),
opcodes = listOf(
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_STATIC,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_VIRTUAL
),
strings = listOf("activity"), strings = listOf("activity"),
customFingerprint = { methodDef, _ -> methodDef.name == "onCreate" }, customFingerprint = { methodDef, _ -> methodDef.name == "onCreate" },
) )

View file

@ -14,7 +14,7 @@ import kotlin.random.Random
name = "Spoof device ID", name = "Spoof device ID",
description = "Spoofs device ID to mitigate manual bans by developers.", description = "Spoofs device ID to mitigate manual bans by developers.",
dependencies = [SignatureDetectionPatch::class], dependencies = [SignatureDetectionPatch::class],
compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.32.0"])] compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.37.0"])]
) )
@Suppress("unused") @Suppress("unused")
object SpoofDeviceIdPatch : BytecodePatch( object SpoofDeviceIdPatch : BytecodePatch(

View file

@ -1,9 +1,21 @@
package app.revanced.patches.photomath.detection.deviceid.fingerprints package app.revanced.patches.photomath.detection.deviceid.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object GetDeviceIdFingerprint : MethodFingerprint( internal object GetDeviceIdFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;", returnType = "Ljava/lang/String;",
strings = listOf("androidId", "android_id"), opcodes = listOf(
Opcode.SGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
),
parameters = listOf() parameters = listOf()
) )

View file

@ -5,11 +5,9 @@ import com.android.tools.smali.dexlib2.Opcode
internal object CheckSignatureFingerprint : MethodFingerprint( internal object CheckSignatureFingerprint : MethodFingerprint(
strings = listOf( strings = listOf(
"packageInfo.signatures", "signatures",
"currentSignature"
), ),
opcodes = listOf( opcodes = listOf(
Opcode.CONST_STRING,
Opcode.CONST_STRING, Opcode.CONST_STRING,
Opcode.INVOKE_STATIC, Opcode.INVOKE_STATIC,
Opcode.INVOKE_STATIC, Opcode.INVOKE_STATIC,

View file

@ -13,7 +13,7 @@ import app.revanced.util.exception
name = "Hide update popup", name = "Hide update popup",
description = "Prevents the update popup from showing up.", description = "Prevents the update popup from showing up.",
dependencies = [SignatureDetectionPatch::class], dependencies = [SignatureDetectionPatch::class],
compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.32.0"])] compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.37.0"])]
) )
@Suppress("unused") @Suppress("unused")
object HideUpdatePopupPatch : BytecodePatch( object HideUpdatePopupPatch : BytecodePatch(

View file

@ -13,7 +13,7 @@ import app.revanced.patches.photomath.misc.unlock.plus.fingerprints.IsPlusUnlock
@Patch( @Patch(
name = "Unlock plus", name = "Unlock plus",
dependencies = [SignatureDetectionPatch::class, EnableBookpointPatch::class], dependencies = [SignatureDetectionPatch::class, EnableBookpointPatch::class],
compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.32.0"])] compatiblePackages = [CompatiblePackage("com.microblink.photomath", ["8.37.0"])]
) )
@Suppress("unused") @Suppress("unused")
object UnlockPlusPatch : BytecodePatch( object UnlockPlusPatch : BytecodePatch(

View file

@ -0,0 +1,59 @@
package app.revanced.patches.piccomafr.misc
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.piccomafr.misc.fingerprints.GetAndroidIDFingerprint
import app.revanced.util.exception
@Patch(
name = "Spoof Android device ID",
description = "Spoofs the Android device ID used by the app for account authentication." +
"This can be used to copy the account to another device.",
compatiblePackages = [
CompatiblePackage(
"com.piccomaeurope.fr",
[
"6.4.0",
"6.4.1",
"6.4.2",
"6.4.3",
"6.4.4",
"6.4.5",
"6.5.0",
"6.5.1",
"6.5.2",
"6.5.3",
"6.5.4",
"6.6.0",
"6.6.1",
"6.6.2",
],
),
],
use = false,
)
@Suppress("unused")
object SpoofAndroidDeviceIdPatch : BytecodePatch(
setOf(GetAndroidIDFingerprint),
) {
private var androidDeviceId =
stringPatchOption(
key = "android-device-id",
default = "0011223344556677",
title = "Android device ID",
description = "The Android device ID to spoof to.",
required = true,
) { it!!.matches("[A-Fa-f0-9]{16}".toRegex()) }
override fun execute(context: BytecodeContext) = GetAndroidIDFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
const-string v0, "$androidDeviceId"
return-object v0
""",
) ?: throw GetAndroidIDFingerprint.exception
}

View file

@ -0,0 +1,16 @@
package app.revanced.patches.piccomafr.misc.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object GetAndroidIDFingerprint : MethodFingerprint(
parameters = listOf("Landroid/content/Context"),
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
strings = listOf(
"context",
"android_id"
),
returnType = "Ljava/lang/String"
)

View file

@ -0,0 +1,80 @@
package app.revanced.patches.piccomafr.tracking
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.piccomafr.tracking.fingerprints.AppMesurementFingerprint
import app.revanced.patches.piccomafr.tracking.fingerprints.FacebookSDKFingerprint
import app.revanced.patches.piccomafr.tracking.fingerprints.FirebaseInstallFingerprint
import app.revanced.util.exception
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Patch(
name = "Disable tracking",
description = "Disables tracking by replacing tracking URLs with example.com.",
compatiblePackages = [
CompatiblePackage(
"com.piccomaeurope.fr",
[
"6.4.0",
"6.4.1",
"6.4.2",
"6.4.3",
"6.4.4",
"6.4.5",
"6.5.0",
"6.5.1",
"6.5.2",
"6.5.3",
"6.5.4",
"6.6.0",
"6.6.1",
"6.6.2",
],
),
],
)
@Suppress("unused")
object DisableTrackingPatch : BytecodePatch(
setOf(FacebookSDKFingerprint, FirebaseInstallFingerprint, AppMesurementFingerprint),
) {
override fun execute(context: BytecodeContext) {
FacebookSDKFingerprint.result?.mutableMethod?.apply {
getInstructions().filter { instruction ->
instruction.opcode == Opcode.CONST_STRING
}.forEach { instruction ->
instruction as OneRegisterInstruction
replaceInstruction(
instruction.location.index,
"const-string v${instruction.registerA}, \"example.com\"",
)
}
} ?: throw FacebookSDKFingerprint.exception
FirebaseInstallFingerprint.result?.mutableMethod?.apply {
getInstructions().filter {
it.opcode == Opcode.CONST_STRING
}.filter {
it.getReference<StringReference>()?.string == "firebaseinstallations.googleapis.com"
}.forEach { instruction ->
instruction as OneRegisterInstruction
replaceInstruction(
instruction.location.index,
"const-string v${instruction.registerA}, \"example.com\"",
)
}
} ?: throw FirebaseInstallFingerprint.exception
AppMesurementFingerprint.result?.mutableMethod?.addInstruction(0, "return-void")
?: throw AppMesurementFingerprint.exception
}
}

View file

@ -0,0 +1,15 @@
package app.revanced.patches.piccomafr.tracking.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object AppMesurementFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
strings = listOf(
"config/app/",
"Fetching remote configuration"
),
returnType = "V"
)

View file

@ -0,0 +1,15 @@
package app.revanced.patches.piccomafr.tracking.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object FacebookSDKFingerprint : MethodFingerprint(
accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR,
strings = listOf(
"instagram.com",
"facebook.com"
),
returnType = "V"
)

View file

@ -0,0 +1,13 @@
package app.revanced.patches.piccomafr.tracking.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object FirebaseInstallFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PRIVATE.value,
strings = listOf(
"https://%s/%s/%s",
"firebaseinstallations.googleapis.com"
)
)

View file

@ -10,8 +10,10 @@ import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.BasicAuthorizationFingerprint import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.BasicAuthorizationFingerprint
import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.BuildAuthorizationStringFingerprint import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.BuildAuthorizationStringFingerprint
import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.GetUserAgentFingerprint import app.revanced.patches.reddit.customclients.redditisfun.api.fingerprints.GetUserAgentFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Suppress("unused") @Suppress("unused")
object SpoofClientPatch : BaseSpoofClientPatch( object SpoofClientPatch : BaseSpoofClientPatch(
@ -20,8 +22,8 @@ object SpoofClientPatch : BaseSpoofClientPatch(
userAgentFingerprints = setOf(GetUserAgentFingerprint), userAgentFingerprints = setOf(GetUserAgentFingerprint),
compatiblePackages = setOf( compatiblePackages = setOf(
CompatiblePackage("com.andrewshu.android.reddit"), CompatiblePackage("com.andrewshu.android.reddit"),
CompatiblePackage("com.andrewshu.android.redditdonation") CompatiblePackage("com.andrewshu.android.redditdonation"),
) ),
) { ) {
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) { override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
/** /**
@ -59,7 +61,23 @@ object SpoofClientPatch : BaseSpoofClientPatch(
""" """
const-string v0, "$userAgent" const-string v0, "$userAgent"
return-object v0 return-object v0
""" """,
)
}
override fun Set<MethodFingerprintResult>.patchMiscellaneous(context: BytecodeContext) {
// Reddit messed up and does not append a redirect uri to the authorization url to old.reddit.com/login.
// Replace old.reddit.com with ssl.reddit.com to fix this.
BuildAuthorizationStringFingerprint.result!!.mutableMethod.apply {
val index = indexOfFirstInstruction {
getReference<StringReference>()?.contains("old.reddit.com") == true
}
val targetRegister = getInstruction<OneRegisterInstruction>(index).registerA
replaceInstruction(
index,
"const-string v$targetRegister, \"https://ssl.reddit.com/api/v1/authorize.compact\"",
) )
} }
} }
}

View file

@ -6,7 +6,7 @@ import app.revanced.patcher.patch.RawResourcePatch
import kotlin.math.max import kotlin.math.max
abstract class BaseHexPatch : RawResourcePatch() { abstract class BaseHexPatch : RawResourcePatch() {
internal abstract val replacements: List<Replacement> abstract val replacements: List<Replacement>
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
replacements.groupBy { it.targetFilePath }.forEach { (targetFilePath, replacements) -> replacements.groupBy { it.targetFilePath }.forEach { (targetFilePath, replacements) ->

View file

@ -8,10 +8,13 @@ import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint.IRegisterResolver import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint.IRegisterResolver
import app.revanced.patches.shared.misc.integrations.fingerprints.ReVancedUtilsPatchesVersionFingerprint import app.revanced.patches.shared.misc.integrations.fingerprints.ReVancedUtilsPatchesVersionFingerprint
import app.revanced.util.exception
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.jar.JarFile import java.util.jar.JarFile
abstract class BaseIntegrationsPatch( abstract class BaseIntegrationsPatch(
@ -73,8 +76,8 @@ abstract class BaseIntegrationsPatch(
val urlString = classUrl.toString() val urlString = classUrl.toString()
if (urlString.startsWith("jar:file:")) { if (urlString.startsWith("jar:file:")) {
val end = urlString.indexOf('!') val end = urlString.lastIndexOf('!')
return urlString.substring("jar:file:".length, end) return URLDecoder.decode(urlString.substring("jar:file:".length, end), StandardCharsets.UTF_8)
} }
} }
throw IllegalStateException("Not running from inside a JAR file.") throw IllegalStateException("Not running from inside a JAR file.")
@ -137,7 +140,7 @@ abstract class BaseIntegrationsPatch(
"invoke-static/range { v$contextRegister .. v$contextRegister }, " + "invoke-static/range { v$contextRegister .. v$contextRegister }, " +
"$integrationsDescriptor->setContext(Landroid/content/Context;)V", "$integrationsDescriptor->setContext(Landroid/content/Context;)V",
) )
} ?: throw PatchException("Could not find hook target fingerprint.") } ?: throw this.exception
} }
interface IHookInsertIndexResolver : (Method) -> Int { interface IHookInsertIndexResolver : (Method) -> Int {

View file

@ -0,0 +1,21 @@
package app.revanced.patches.tumblr.annoyances.adfree
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch
@Patch(
name = "Disable Ad-Free Banner",
description = "Disables the banner with a frog, prompting you to buy Tumblr Ad-Free.",
dependencies = [OverrideFeatureFlagsPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")],
)
@Suppress("unused")
object DisableAdFreeBannerPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
// Disable the "AD_FREE_CTA_BANNER" ("Whether or not to show ad free prompt") feature flag.
OverrideFeatureFlagsPatch.addOverride("adFreeCtaBanner", "false")
}
}

View file

@ -62,7 +62,17 @@ object SettingsPatch : BytecodePatch(
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
PreferenceScreen.MISC.OTHER.addPreferences( PreferenceScreen.MISC.OTHER.addPreferences(
SwitchPreference("revanced_debug") // The debug setting is shared across multiple apps and the key must be the same.
// But the title and summary must be different, otherwise when the strings file is flattened
// for Crowdin push, Crowdin gets confused by the duplicate keys.
// FIXME: Ideally the shared debug strings are extracted into a common app group
// and then both apps import that. But for now unique unique title and summary keys also works.
SwitchPreference(
key = "revanced_debug",
titleKey = "revanced_twitch_debug_title",
summaryOnKey = "revanced_twitch_debug_summary_on",
summaryOffKey = "revanced_twitch_debug_summary_off"
)
) )
// Hook onCreate to handle fragment creation // Hook onCreate to handle fragment creation

View file

@ -12,7 +12,7 @@ import app.revanced.patches.warnwetter.misc.promocode.fingerprints.PromoCodeUnlo
name = "Promo code unlock", name = "Promo code unlock",
description = "Disables the validation of promo code. Any code will work to unlock all features.", description = "Disables the validation of promo code. Any code will work to unlock all features.",
dependencies = [FirebaseGetCertPatch::class], dependencies = [FirebaseGetCertPatch::class],
compatiblePackages = [CompatiblePackage("de.dwd.warnapp")] compatiblePackages = [CompatiblePackage("de.dwd.warnapp", ["4.2.2"])]
) )
@Suppress("unused") @Suppress("unused")
object PromoCodeUnlockPatch : BytecodePatch( object PromoCodeUnlockPatch : BytecodePatch(

View file

@ -34,6 +34,7 @@ object HideAdsResourcePatch : ResourcePatch() {
SwitchPreference("revanced_hide_self_sponsor_ads"), SwitchPreference("revanced_hide_self_sponsor_ads"),
SwitchPreference("revanced_hide_products_banner"), SwitchPreference("revanced_hide_products_banner"),
SwitchPreference("revanced_hide_shopping_links"), SwitchPreference("revanced_hide_shopping_links"),
SwitchPreference("revanced_hide_visit_store_button"),
SwitchPreference("revanced_hide_web_search_results"), SwitchPreference("revanced_hide_web_search_results"),
SwitchPreference("revanced_hide_merchandise_banners"), SwitchPreference("revanced_hide_merchandise_banners"),
) )

View file

@ -65,8 +65,7 @@ object HideButtonsPatch : ResourcePatch() {
SwitchPreference("revanced_hide_download_button"), SwitchPreference("revanced_hide_download_button"),
SwitchPreference("revanced_hide_thanks_button"), SwitchPreference("revanced_hide_thanks_button"),
SwitchPreference("revanced_hide_clip_button"), SwitchPreference("revanced_hide_clip_button"),
SwitchPreference("revanced_hide_playlist_button"), SwitchPreference("revanced_hide_playlist_button")
SwitchPreference("revanced_hide_shop_button")
), ),
) )
) )

View file

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.buttons.navigation package app.revanced.patches.youtube.layout.buttons.navigation
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
@ -12,11 +13,16 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sor
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.layout.buttons.navigation.fingerprints.ANDROID_AUTOMOTIVE_STRING import app.revanced.patches.youtube.layout.buttons.navigation.fingerprints.ANDROID_AUTOMOTIVE_STRING
import app.revanced.patches.youtube.layout.buttons.navigation.fingerprints.AddCreateButtonViewFingerprint import app.revanced.patches.youtube.layout.buttons.navigation.fingerprints.AddCreateButtonViewFingerprint
import app.revanced.patches.youtube.layout.buttons.navigation.fingerprints.CreatePivotBarFingerprint
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( @Patch(
name = "Navigation buttons", name = "Navigation buttons",
@ -49,14 +55,17 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
"19.08.36", "19.08.36",
"19.09.38", "19.09.38",
"19.10.39", "19.10.39",
"19.11.43" "19.11.43",
], ],
), ),
], ],
) )
@Suppress("unused") @Suppress("unused")
object NavigationButtonsPatch : BytecodePatch( object NavigationButtonsPatch : BytecodePatch(
setOf(AddCreateButtonViewFingerprint), setOf(
AddCreateButtonViewFingerprint,
CreatePivotBarFingerprint,
),
) { ) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR = private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/NavigationButtonsPatch;" "Lapp/revanced/integrations/youtube/patches/NavigationButtonsPatch;"
@ -74,6 +83,7 @@ object NavigationButtonsPatch : BytecodePatch(
SwitchPreference("revanced_hide_create_button"), SwitchPreference("revanced_hide_create_button"),
SwitchPreference("revanced_hide_subscriptions_button"), SwitchPreference("revanced_hide_subscriptions_button"),
SwitchPreference("revanced_switch_create_with_notifications_button"), SwitchPreference("revanced_switch_create_with_notifications_button"),
SwitchPreference("revanced_hide_navigation_button_labels"),
), ),
), ),
) )
@ -99,6 +109,21 @@ object NavigationButtonsPatch : BytecodePatch(
} }
} ?: throw AddCreateButtonViewFingerprint.exception } ?: throw AddCreateButtonViewFingerprint.exception
// Hide navigation button labels.
CreatePivotBarFingerprint.result?.mutableMethod?.apply {
val setTextIndex = indexOfFirstInstruction {
getReference<MethodReference>()?.name == "setText"
}
val targetRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC
addInstruction(
setTextIndex,
"invoke-static { v$targetRegister }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->hideNavigationButtonLabels(Landroid/widget/TextView;)V",
)
} ?: throw CreatePivotBarFingerprint.exception
// Hook navigation button created, in order to hide them. // Hook navigation button created, in order to hide them.
NavigationBarHookPatch.hookNavigationButtonCreated(INTEGRATIONS_CLASS_DESCRIPTOR) NavigationBarHookPatch.hookNavigationButtonCreated(INTEGRATIONS_CLASS_DESCRIPTOR)
} }

View file

@ -0,0 +1,20 @@
package app.revanced.patches.youtube.layout.buttons.navigation.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
internal object CreatePivotBarFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf(
"Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;",
"Landroid/widget/TextView;",
"Ljava/lang/CharSequence;",
),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID,
),
)

View file

@ -58,7 +58,8 @@ object CommentsPatch : ResourcePatch() {
"revanced_comments_screen", "revanced_comments_screen",
preferences = setOf( preferences = setOf(
SwitchPreference("revanced_hide_preview_comment"), SwitchPreference("revanced_hide_preview_comment"),
SwitchPreference("revanced_hide_comments_section") SwitchPreference("revanced_hide_comments_section"),
SwitchPreference("revanced_hide_comment_timestamp_and_emoji_buttons")
), ),
sorting = PreferenceScreen.Sorting.UNSORTED sorting = PreferenceScreen.Sorting.UNSORTED
) )

View file

@ -103,7 +103,6 @@ object HideLayoutComponentsPatch : BytecodePatch(
SwitchPreference("revanced_hide_info_panels"), SwitchPreference("revanced_hide_info_panels"),
SwitchPreference("revanced_hide_join_membership_button"), SwitchPreference("revanced_hide_join_membership_button"),
SwitchPreference("revanced_hide_medical_panels"), SwitchPreference("revanced_hide_medical_panels"),
SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_quick_actions"), SwitchPreference("revanced_hide_quick_actions"),
SwitchPreference("revanced_hide_related_videos"), SwitchPreference("revanced_hide_related_videos"),
SwitchPreference("revanced_hide_subscribers_community_guidelines"), SwitchPreference("revanced_hide_subscribers_community_guidelines"),
@ -122,6 +121,7 @@ object HideLayoutComponentsPatch : BytecodePatch(
SwitchPreference("revanced_hide_mix_playlists"), SwitchPreference("revanced_hide_mix_playlists"),
SwitchPreference("revanced_hide_movies_section"), SwitchPreference("revanced_hide_movies_section"),
SwitchPreference("revanced_hide_notify_me_button"), SwitchPreference("revanced_hide_notify_me_button"),
SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_search_result_recommendations"), SwitchPreference("revanced_hide_search_result_recommendations"),
SwitchPreference("revanced_hide_search_result_shelf_header"), SwitchPreference("revanced_hide_search_result_shelf_header"),
SwitchPreference("revanced_hide_show_more_button"), SwitchPreference("revanced_hide_show_more_button"),

View file

@ -63,7 +63,6 @@ object HidePlayerFlyoutMenuPatch : ResourcePatch() {
SwitchPreference("revanced_hide_player_flyout_additional_settings"), SwitchPreference("revanced_hide_player_flyout_additional_settings"),
SwitchPreference("revanced_hide_player_flyout_loop_video"), SwitchPreference("revanced_hide_player_flyout_loop_video"),
SwitchPreference("revanced_hide_player_flyout_ambient_mode"), SwitchPreference("revanced_hide_player_flyout_ambient_mode"),
SwitchPreference("revanced_hide_player_flyout_report"),
SwitchPreference("revanced_hide_player_flyout_help"), SwitchPreference("revanced_hide_player_flyout_help"),
SwitchPreference("revanced_hide_player_flyout_speed"), SwitchPreference("revanced_hide_player_flyout_speed"),
SwitchPreference("revanced_hide_player_flyout_lock_screen"), SwitchPreference("revanced_hide_player_flyout_lock_screen"),

View file

@ -13,11 +13,14 @@ import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.indexOfIdResourceOrThrow import app.revanced.util.indexOfIdResourceOrThrow
import app.revanced.util.injectHideViewCall import app.revanced.util.injectHideViewCall
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( @Patch(
name = "Hide Shorts components", name = "Hide Shorts components",
@ -155,17 +158,24 @@ object HideShortsComponentsPatch : BytecodePatch(
} }
private enum class ShortsButtons(private val resourceName: String, private val methodName: String) { private enum class ShortsButtons(private val resourceName: String, private val methodName: String) {
LIKE("reel_dyn_like", "hideLikeButton"),
DISLIKE("reel_dyn_dislike", "hideDislikeButton"),
COMMENTS("reel_dyn_comment", "hideShortsCommentsButton"), COMMENTS("reel_dyn_comment", "hideShortsCommentsButton"),
REMIX("reel_dyn_remix", "hideShortsRemixButton"), REMIX("reel_dyn_remix", "hideShortsRemixButton"),
SHARE("reel_dyn_share", "hideShortsShareButton"), SHARE("reel_dyn_share", "hideShortsShareButton");
;
fun injectHideCall(method: MutableMethod) { fun injectHideCall(method: MutableMethod) {
val referencedIndex = method.indexOfIdResourceOrThrow(resourceName) val referencedIndex = method.indexOfIdResourceOrThrow(resourceName)
val setIdIndex = referencedIndex + 1 val instruction = method.implementation!!.instructions
.subList(referencedIndex, referencedIndex + 20)
.first {
it.opcode == Opcode.INVOKE_VIRTUAL && it.getReference<MethodReference>()?.name == "setId"
}
val setIdIndex = instruction.location.index
val viewRegister = method.getInstruction<FiveRegisterInstruction>(setIdIndex).registerC val viewRegister = method.getInstruction<FiveRegisterInstruction>(setIdIndex).registerC
method.injectHideViewCall(setIdIndex, viewRegister, FILTER_CLASS_DESCRIPTOR, methodName) method.injectHideViewCall(setIdIndex + 1, viewRegister, FILTER_CLASS_DESCRIPTOR, methodName)
} }
} }
} }

View file

@ -41,6 +41,7 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
SwitchPreference("revanced_hide_shorts_shop_button"), SwitchPreference("revanced_hide_shorts_shop_button"),
SwitchPreference("revanced_hide_shorts_tagged_products"), SwitchPreference("revanced_hide_shorts_tagged_products"),
SwitchPreference("revanced_hide_shorts_search_suggestions"), SwitchPreference("revanced_hide_shorts_search_suggestions"),
SwitchPreference("revanced_hide_shorts_super_thanks_button"),
SwitchPreference("revanced_hide_shorts_location_label"), SwitchPreference("revanced_hide_shorts_location_label"),
SwitchPreference("revanced_hide_shorts_channel_bar"), SwitchPreference("revanced_hide_shorts_channel_bar"),
SwitchPreference("revanced_hide_shorts_info_panel"), SwitchPreference("revanced_hide_shorts_info_panel"),

View file

@ -0,0 +1,335 @@
package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.getReference
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@Patch(
name = "Spoof client",
description = "Spoofs the client to allow video playback.",
dependencies = [
SpoofClientResourcePatch::class,
PlayerResponseMethodHookPatch::class,
SettingsPatch::class,
AddResourcesPatch::class,
UserAgentClientSpoofPatch::class,
PlayerResponseMethodHookPatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube",
[
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39",
"18.49.37",
"19.01.34",
"19.02.39",
"19.03.36",
"19.04.38",
"19.05.36",
"19.06.39",
"19.07.40",
"19.08.36",
"19.09.38",
"19.10.39",
"19.11.43",
],
),
],
)
object SpoofClientPatch : BytecodePatch(
setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint,
// Storyboard spoof.
StoryboardRendererSpecFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
PlayerResponseModelImplGeneralFingerprint,
StoryboardRendererDecoderSpecFingerprint,
),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_client_screen",
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_client"),
SwitchPreference("revanced_spoof_client_use_ios"),
),
),
)
// region Block /initplayback requests to fall back to /get_watch requests.
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
addInstructions(
moveUriStringIndex + 1,
"""
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
""",
)
}
}
// endregion
// region Block /get_watch requests to fall back to /player requests.
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
addInstructions(
invokeToStringIndex,
"""
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
move-result-object v$uriRegister
""",
)
}
}
// endregion
// region Get field references to be used below.
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod
.getInstructions().first { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT &&
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
// Client info object's client type field.
val clientInfoClientTypeField = result.mutableMethod
.getInstruction(result.scanResult.patternScanResult!!.endIndex)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientTypeField")
// Client info object's client version field.
val clientInfoClientVersionField = result.mutableMethod
.getInstruction(result.scanResult.stringsScanResult!!.matches.first().index + 1)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientVersionField")
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}
// endregion
// region Spoof client type for /player requests.
CreatePlayerRequestBodyFingerprint.resultOrThrow().let { result ->
val setClientInfoMethodName = "patch_setClientInfo"
val checkCastIndex = result.scanResult.patternScanResult!!.startIndex
var clientInfoContainerClassName: String
result.mutableMethod.apply {
val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
val requestMessageInstanceRegister = checkCastInstruction.registerA
clientInfoContainerClassName = checkCastInstruction.getReference<TypeReference>()!!.type
addInstruction(
checkCastIndex + 1,
"invoke-static { v$requestMessageInstanceRegister }," +
" ${result.classDef.type}->$setClientInfoMethodName($clientInfoContainerClassName)V",
)
}
// Change requestMessage.clientInfo.clientType and requestMessage.clientInfo.clientVersion to the spoofed values.
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
result.mutableClass.methods.add(
ImmutableMethod(
result.mutableClass.type,
setClientInfoMethodName,
listOf(ImmutableMethodParameter(clientInfoContainerClassName, null, "clientInfoContainer")),
"V",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
null,
MutableMethodImplementation(3),
).toMutable().apply {
addInstructions(
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isClientSpoofingEnabled()Z
move-result v0
if-eqz v0, :disabled
iget-object v0, p0, $clientInfoField
# Set client type to the spoofed value.
iget v1, v0, $clientInfoClientTypeField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientTypeId(I)I
move-result v1
iput v1, v0, $clientInfoClientTypeField
# Set client version to the spoofed value.
iget-object v1, v0, $clientInfoClientVersionField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField
:disabled
return-void
""",
)
},
)
}
// endregion
// region Fix storyboard if Android Testsuite is used.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
)
// Hook recommended seekbar thumbnails quality level for regular videos.
StoryboardRendererDecoderRecommendedLevelFingerprint.resultOrThrow().let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val originalValueRegister =
getInstruction<OneRegisterInstruction>(endIndex).registerA
addInstructions(
endIndex + 1,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
}
// Hook the recommended precise seeking thumbnails quality.
PlayerResponseModelImplRecommendedLevelFingerprint.resultOrThrow().let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val originalValueRegister =
getInstruction<OneRegisterInstruction>(endIndex).registerA
addInstructions(
endIndex,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
}
// TODO: Hook the seekbar recommended level for Shorts to fix Shorts low quality seekbar thumbnails.
/**
* Hook StoryBoard renderer url.
*/
PlayerResponseModelImplGeneralFingerprint.resultOrThrow().let {
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
addInstructions(
getStoryBoardIndex,
"""
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
}
// Hook the seekbar thumbnail decoder, required for Shorts.
StoryboardRendererDecoderSpecFingerprint.resultOrThrow().let {
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
it.mutableMethod.apply {
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
addInstructions(
storyBoardUrlIndex + 1,
"""
invoke-static { v$getStoryBoardRegister }, ${INTEGRATIONS_CLASS_DESCRIPTOR}->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
}
StoryboardRendererSpecFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val storyBoardUrlParams = "p0"
addInstructions(
0,
"""
if-nez $storyBoardUrlParams, :ignore
invoke-static { $storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object $storyBoardUrlParams
:ignore
nop
""",
)
}
}
// endregion
}
}

View file

@ -0,0 +1,18 @@
package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
@Patch(dependencies = [ResourceMappingPatch::class])
internal object SpoofClientResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1
override fun execute(context: ResourceContext) {
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
"id",
"thumbnail",
]
}
}

View file

@ -68,7 +68,7 @@ object SpoofSignaturePatch : BytecodePatch(
// Hook the player parameters. // Hook the player parameters.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter( PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Z)Ljava/lang/String;", "$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
) )
// Force the seekbar time and chapters to always show up. // Force the seekbar time and chapters to always show up.
@ -104,7 +104,7 @@ object SpoofSignaturePatch : BytecodePatch(
} }
// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view. // If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
ScrubbedPreviewLayoutFingerprint.result?.apply { SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
val endIndex = scanResult.patternScanResult!!.endIndex val endIndex = scanResult.patternScanResult!!.endIndex
mutableMethod.apply { mutableMethod.apply {
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
@ -116,7 +116,7 @@ object SpoofSignaturePatch : BytecodePatch(
""", """,
) )
} }
} ?: throw ScrubbedPreviewLayoutFingerprint.exception } ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception
/** /**
* Hook StoryBoard renderer url * Hook StoryBoard renderer url

View file

@ -4,9 +4,9 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class]) @Patch(dependencies = [ResourceMappingPatch::class])
@Deprecated("This patch will be removed in the future.")
object SpoofSignatureResourcePatch : ResourcePatch() { object SpoofSignatureResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1 internal var scrubbedPreviewThumbnailResourceId: Long = -1

View file

@ -2,8 +2,6 @@ package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall import app.revanced.patches.all.misc.transformation.IMethodCall
@ -16,14 +14,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( object UserAgentClientSpoofPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
name = "Client spoof",
description = "Spoofs the client to allow video playback.",
compatiblePackages = [
CompatiblePackage("com.google.android.youtube"),
],
)
object ClientSpoofPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
private const val ORIGINAL_PACKAGE_NAME = "com.google.android.youtube" private const val ORIGINAL_PACKAGE_NAME = "com.google.android.youtube"
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE = private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE =
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;" "Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"

View file

@ -0,0 +1,16 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object BuildInitPlaybackRequestFingerprint : MethodFingerprint(
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
opcodes = listOf(
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with.
),
strings = listOf(
"Content-Type",
"Range",
),
)

View file

@ -0,0 +1,21 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object BuildPlayerRequestURIFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;",
opcodes = listOf(
Opcode.INVOKE_VIRTUAL, // Register holds player request URI.
Opcode.MOVE_RESULT_OBJECT,
Opcode.IPUT_OBJECT,
Opcode.IGET_OBJECT,
Opcode.MONITOR_EXIT,
Opcode.RETURN_OBJECT,
),
strings = listOf(
"youtubei/v1",
"key",
"asig",
),
)

View file

@ -0,0 +1,15 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object CreatePlayerRequestBodyFingerprint : MethodFingerprint(
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.IGET,
Opcode.AND_INT_LIT16,
),
strings = listOf("ms"),
)

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
opcodes = listOf( opcodes = listOf(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497) methodDef.containsWideLiteralInstructionValue(55735497)
} },
) )

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint
opcodes = listOf( opcodes = listOf(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(70276274) methodDef.containsWideLiteralInstructionValue(70276274)
} },
) )

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFinge
opcodes = listOf( opcodes = listOf(
Opcode.SGET_OBJECT, Opcode.SGET_OBJECT,
Opcode.IGET, Opcode.IGET,
Opcode.RETURN Opcode.RETURN,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497) methodDef.containsWideLiteralInstructionValue(55735497)
} },
) )

View file

@ -1,7 +1,7 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch import app.revanced.patches.youtube.misc.fix.playback.SpoofClientResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -23,5 +23,5 @@ internal object ScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
Opcode.IPUT_OBJECT, // preview imageview Opcode.IPUT_OBJECT, // preview imageview
), ),
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to. // This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId } literalSupplier = { SpoofClientResourcePatch.scrubbedPreviewThumbnailResourceId },
) )

View file

@ -0,0 +1,12 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint(
strings = listOf("10.29"),
opcodes = listOf(
Opcode.IGET,
Opcode.IPUT, // Sets ClientInfo.clientId.
),
)

View file

@ -0,0 +1,27 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object SpoofSignaturePatchScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Landroid/content/Context;", "Landroid/util/AttributeSet;", "I", "I"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_VIRTUAL,
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IPUT_OBJECT, // preview imageview
),
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId },
)

View file

@ -17,7 +17,7 @@ internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFin
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT Opcode.MOVE_RESULT,
), ),
strings = listOf("#-1#") strings = listOf("#-1#"),
) )

View file

@ -19,5 +19,5 @@ internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
Opcode.CONST_4, Opcode.CONST_4,
Opcode.IF_NEZ, Opcode.IF_NEZ,
), ),
strings = listOf("#-1#") strings = listOf("#-1#"),
) )

View file

@ -3,7 +3,7 @@ package app.revanced.patches.youtube.misc.gms
import app.revanced.patches.shared.fingerprints.CastContextFetchFingerprint import app.revanced.patches.shared.fingerprints.CastContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.BaseGmsCoreSupportPatch import app.revanced.patches.shared.misc.gms.BaseGmsCoreSupportPatch
import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch
import app.revanced.patches.youtube.misc.fix.playback.ClientSpoofPatch import app.revanced.patches.youtube.misc.fix.playback.SpoofClientPatch
import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorGroupIdOption import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorGroupIdOption
@ -27,13 +27,18 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
integrationsPatchDependency = IntegrationsPatch::class, integrationsPatchDependency = IntegrationsPatch::class,
dependencies = setOf( dependencies = setOf(
HideCastButtonPatch::class, HideCastButtonPatch::class,
ClientSpoofPatch::class, SpoofClientPatch::class,
), ),
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch, gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
compatiblePackages = setOf( compatiblePackages = setOf(
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", "com.google.android.youtube",
setOf( setOf(
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
@ -46,7 +51,7 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
"19.08.36", "19.08.36",
"19.09.38", "19.09.38",
"19.10.39", "19.10.39",
"19.11.43" "19.11.43",
), ),
), ),
), ),

View file

@ -119,7 +119,7 @@ object VideoInformationPatch : BytecodePatch(
// Call before any other video id hooks, // Call before any other video id hooks,
// so they can use VideoInformation and check if the video id is for a Short. // so they can use VideoInformation and check if the video id is for a Short.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameterBeforeVideoId( PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameterBeforeVideoId(
"$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseSignature(Ljava/lang/String;Z)Ljava/lang/String;") "$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseSignature(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;")
/* /*
* Set the video time method * Set the video time method

View file

@ -24,24 +24,39 @@ object PlayerResponseMethodHookPatch :
private const val PARAMETER_PROTO_BUFFER = 3 private const val PARAMETER_PROTO_BUFFER = 3
private const val PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11 private const val PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11
// Temporary 4-bit registers used to pass the parameters to integrations. // Registers used to pass the parameters to integrations.
private const val REGISTER_VIDEO_ID = 0 private var playerResponseMethodCopyRegisters = false
private const val REGISTER_PROTO_BUFFER = 1 private lateinit var REGISTER_VIDEO_ID : String
private const val REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = 2 private lateinit var REGISTER_PROTO_BUFFER : String
private lateinit var REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING : String
private lateinit var playerResponseMethod: MutableMethod private lateinit var playerResponseMethod: MutableMethod
private var numberOfInstructionsAdded = 0 private var numberOfInstructionsAdded = 0
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod
?: throw PlayerParameterBuilderFingerprint.exception ?: throw PlayerParameterBuilderFingerprint.exception
// On some app targets the method has too many registers pushing the parameters past v15.
// If needed, move the parameters to 4-bit registers so they can be passed to integrations.
playerResponseMethodCopyRegisters = playerResponseMethod.implementation!!.registerCount -
playerResponseMethod.parameterTypes.size + PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING > 15
if (playerResponseMethodCopyRegisters) {
REGISTER_VIDEO_ID = "v0"
REGISTER_PROTO_BUFFER = "v1"
REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = "v2"
} else {
REGISTER_VIDEO_ID = "p$PARAMETER_VIDEO_ID"
REGISTER_PROTO_BUFFER = "p$PARAMETER_PROTO_BUFFER"
REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = "p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING"
}
} }
override fun close() { override fun close() {
fun hookVideoId(hook: Hook) { fun hookVideoId(hook: Hook) {
playerResponseMethod.addInstruction( playerResponseMethod.addInstruction(
0, "invoke-static {v$REGISTER_VIDEO_ID, v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook" 0, "invoke-static {$REGISTER_VIDEO_ID, $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook"
) )
numberOfInstructionsAdded++ numberOfInstructionsAdded++
} }
@ -50,8 +65,8 @@ object PlayerResponseMethodHookPatch :
playerResponseMethod.addInstructions( playerResponseMethod.addInstructions(
0, 0,
""" """
invoke-static {v$REGISTER_PROTO_BUFFER, v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook invoke-static {$REGISTER_PROTO_BUFFER, $REGISTER_VIDEO_ID, $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook
move-result-object v$REGISTER_PROTO_BUFFER move-result-object $REGISTER_PROTO_BUFFER
""" """
) )
numberOfInstructionsAdded += 2 numberOfInstructionsAdded += 2
@ -67,13 +82,12 @@ object PlayerResponseMethodHookPatch :
videoIdHooks.forEach(::hookVideoId) videoIdHooks.forEach(::hookVideoId)
beforeVideoIdHooks.forEach(::hookProtoBufferParameter) beforeVideoIdHooks.forEach(::hookProtoBufferParameter)
// On some app targets the method has too many registers pushing the parameters past v15. if (playerResponseMethodCopyRegisters) {
// Move the parameters to 4-bit registers so they can be passed to integrations.
playerResponseMethod.addInstructions( playerResponseMethod.addInstructions(
0, """ 0, """
move-object/from16 v$REGISTER_VIDEO_ID, p$PARAMETER_VIDEO_ID move-object/from16 $REGISTER_VIDEO_ID, p$PARAMETER_VIDEO_ID
move-object/from16 v$REGISTER_PROTO_BUFFER, p$PARAMETER_PROTO_BUFFER move-object/from16 $REGISTER_PROTO_BUFFER, p$PARAMETER_PROTO_BUFFER
move/from16 v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING, p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING move/from16 $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING, p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING
""", """,
) )
numberOfInstructionsAdded += 3 numberOfInstructionsAdded += 3
@ -81,9 +95,10 @@ object PlayerResponseMethodHookPatch :
// Move the modified register back. // Move the modified register back.
playerResponseMethod.addInstruction( playerResponseMethod.addInstruction(
numberOfInstructionsAdded, numberOfInstructionsAdded,
"move-object/from16 p$PARAMETER_PROTO_BUFFER, v$REGISTER_PROTO_BUFFER" "move-object/from16 p$PARAMETER_PROTO_BUFFER, $REGISTER_PROTO_BUFFER"
) )
} }
}
internal abstract class Hook(private val methodDescriptor: String) { internal abstract class Hook(private val methodDescriptor: String) {
internal class VideoId(methodDescriptor: String) : Hook(methodDescriptor) internal class VideoId(methodDescriptor: String) : Hook(methodDescriptor)

View file

@ -2,14 +2,18 @@ package app.revanced.patches.youtube.video.videoqualitymenu
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.RecyclerViewTreeHookPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.RecyclerViewTreeHookPatch
import app.revanced.patches.youtube.video.videoqualitymenu.fingerprints.VideoQualityMenuOptionsFingerprint
import app.revanced.patches.youtube.video.videoqualitymenu.fingerprints.VideoQualityMenuViewInflateFingerprint import app.revanced.patches.youtube.video.videoqualitymenu.fingerprints.VideoQualityMenuViewInflateFingerprint
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch( @Patch(
@ -50,7 +54,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
) )
@Suppress("unused") @Suppress("unused")
object RestoreOldVideoQualityMenuPatch : BytecodePatch( object RestoreOldVideoQualityMenuPatch : BytecodePatch(
setOf(VideoQualityMenuViewInflateFingerprint) setOf(VideoQualityMenuViewInflateFingerprint, VideoQualityMenuOptionsFingerprint)
) { ) {
private const val FILTER_CLASS_DESCRIPTOR = private const val FILTER_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/components/VideoQualityMenuFilterPatch;" "Lapp/revanced/integrations/youtube/patches/components/VideoQualityMenuFilterPatch;"
@ -60,7 +64,8 @@ object RestoreOldVideoQualityMenuPatch : BytecodePatch(
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// region Patch for the old type of the video quality menu. // region Patch for the old type of the video quality menu.
// Only used when spoofing to old app version. // Used for regular videos when spoofing to old app version,
// and for the Shorts quality flyout on newer app versions.
VideoQualityMenuViewInflateFingerprint.result?.let { VideoQualityMenuViewInflateFingerprint.result?.let {
it.mutableMethod.apply { it.mutableMethod.apply {
@ -76,6 +81,29 @@ object RestoreOldVideoQualityMenuPatch : BytecodePatch(
} }
} }
// Force YT to add the 'advanced' quality menu for Shorts.
VideoQualityMenuOptionsFingerprint.resultOrThrow().let {
val result = it.scanResult.patternScanResult!!
val startIndex = result.startIndex
val endIndex = result.endIndex
it.mutableMethod.apply {
val freeRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
// A condition controls whether to show the three or four items quality menu.
// Force the four items quality menu to make the "Advanced" item visible, necessary for the patch.
addInstructionsWithLabels(
startIndex + 1,
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceAdvancedVideoQualityMenuCreation()Z
move-result v$freeRegister
if-nez v$freeRegister, :includeAdvancedMenu
""",
ExternalLabel("includeAdvancedMenu", getInstruction(endIndex))
)
}
}
// endregion // endregion
// region Patch for the new type of the video quality menu. // region Patch for the new type of the video quality menu.

View file

@ -13,6 +13,7 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
) )
object RestoreOldVideoQualityMenuResourcePatch : ResourcePatch() { object RestoreOldVideoQualityMenuResourcePatch : ResourcePatch() {
internal var videoQualityBottomSheetListFragmentTitle = -1L internal var videoQualityBottomSheetListFragmentTitle = -1L
internal var videoQualityQuickMenuAdvancedMenuDescription = -1L
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
@ -26,5 +27,10 @@ object RestoreOldVideoQualityMenuResourcePatch : ResourcePatch() {
"layout", "layout",
"video_quality_bottom_sheet_list_fragment_title", "video_quality_bottom_sheet_list_fragment_title",
] ]
videoQualityQuickMenuAdvancedMenuDescription = ResourceMappingPatch[
"string",
"video_quality_quick_menu_advanced_menu_description"
]
} }
} }

View file

@ -0,0 +1,22 @@
package app.revanced.patches.youtube.video.videoqualitymenu.fingerprints
import app.revanced.patches.youtube.video.videoqualitymenu.RestoreOldVideoQualityMenuResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object VideoQualityMenuOptionsFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.STATIC.value,
parameters = listOf("Landroid/content/Context", "L", "L"),
returnType = "[L",
opcodes = listOf(
Opcode.IF_EQZ, // Check if advanced menu should be shown.
Opcode.NEW_ARRAY,
Opcode.APUT_OBJECT,
Opcode.APUT_OBJECT,
Opcode.APUT_OBJECT,
Opcode.RETURN_OBJECT,
Opcode.CONST_4 // Advanced menu code path.
),
literalSupplier = { RestoreOldVideoQualityMenuResourcePatch.videoQualityQuickMenuAdvancedMenuDescription }
)

View file

@ -171,7 +171,7 @@
<string name="revanced_hide_channel_bar_title">Hide channel bar</string> <string name="revanced_hide_channel_bar_title">Hide channel bar</string>
<string name="revanced_hide_channel_bar_summary_on">Channel bar is hidden</string> <string name="revanced_hide_channel_bar_summary_on">Channel bar is hidden</string>
<string name="revanced_hide_channel_bar_summary_off">Channel bar is shown</string> <string name="revanced_hide_channel_bar_summary_off">Channel bar is shown</string>
<string name="revanced_hide_playables_title">Hide playables</string> <string name="revanced_hide_playables_title">Hide Playables</string>
<string name="revanced_hide_playables_summary_on">Playables are hidden</string> <string name="revanced_hide_playables_summary_on">Playables are hidden</string>
<string name="revanced_hide_playables_summary_off">Playables are shown</string> <string name="revanced_hide_playables_summary_off">Playables are shown</string>
<string name="revanced_hide_quick_actions_title">Hide quick actions in fullscreen</string> <string name="revanced_hide_quick_actions_title">Hide quick actions in fullscreen</string>
@ -249,7 +249,7 @@
<string name="revanced_hide_general_ads_summary_on">General ads are hidden</string> <string name="revanced_hide_general_ads_summary_on">General ads are hidden</string>
<string name="revanced_hide_general_ads_summary_off">General ads are shown</string> <string name="revanced_hide_general_ads_summary_off">General ads are shown</string>
<string name="revanced_hide_fullscreen_ads_title">Hide fullscreen ads</string> <string name="revanced_hide_fullscreen_ads_title">Hide fullscreen ads</string>
<string name="revanced_hide_fullscreen_ads_summary_on">Fullscreen ads are hidden</string> <string name="revanced_hide_fullscreen_ads_summary_on">Fullscreen ads are hidden\n\nThis feature is only available for older devices</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Fullscreen ads are shown</string> <string name="revanced_hide_fullscreen_ads_summary_off">Fullscreen ads are shown</string>
<string name="revanced_hide_buttoned_ads_title">Hide buttoned ads</string> <string name="revanced_hide_buttoned_ads_title">Hide buttoned ads</string>
<string name="revanced_hide_buttoned_ads_summary_on">Buttoned ads are hidden</string> <string name="revanced_hide_buttoned_ads_summary_on">Buttoned ads are hidden</string>
@ -266,13 +266,17 @@
<string name="revanced_hide_shopping_links_title">Hide shopping links in video description</string> <string name="revanced_hide_shopping_links_title">Hide shopping links in video description</string>
<string name="revanced_hide_shopping_links_summary_on">Shopping links are hidden</string> <string name="revanced_hide_shopping_links_summary_on">Shopping links are hidden</string>
<string name="revanced_hide_shopping_links_summary_off">Shopping links are shown</string> <string name="revanced_hide_shopping_links_summary_off">Shopping links are shown</string>
<!-- 'Visit store' should be translated with the same localized wording that YouTube displays. -->
<string name="revanced_hide_visit_store_button_title">Hide the \'Visit store\' button on channel pages</string>
<string name="revanced_hide_visit_store_button_summary_on">Button is hidden</string>
<string name="revanced_hide_visit_store_button_summary_off">Button is shown</string>
<string name="revanced_hide_web_search_results_title">Hide web search results</string> <string name="revanced_hide_web_search_results_title">Hide web search results</string>
<string name="revanced_hide_web_search_results_summary_on">Web search results are hidden</string> <string name="revanced_hide_web_search_results_summary_on">Web search results are hidden</string>
<string name="revanced_hide_web_search_results_summary_off">Web search results are shown</string> <string name="revanced_hide_web_search_results_summary_off">Web search results are shown</string>
<string name="revanced_hide_merchandise_banners_title">Hide merchandise banners</string> <string name="revanced_hide_merchandise_banners_title">Hide merchandise banners</string>
<string name="revanced_hide_merchandise_banners_summary_on">Merchandise banners are hidden</string> <string name="revanced_hide_merchandise_banners_summary_on">Merchandise banners are hidden</string>
<string name="revanced_hide_merchandise_banners_summary_off">Merchandise banners are shown</string> <string name="revanced_hide_merchandise_banners_summary_off">Merchandise banners are shown</string>
<string name="revanced_hide_fullscreen_ads_feature_not_available_toast">Could not hide fullscreen ad. Hide setting disabled to prevent issues.</string> <string name="revanced_hide_fullscreen_ads_feature_not_available_toast">Hide fullscreen ads only works with older devices</string>
</patch> </patch>
<patch id="ad.getpremium.HideGetPremiumPatch"> <patch id="ad.getpremium.HideGetPremiumPatch">
<string name="revanced_hide_get_premium_title">Hide YouTube Premium promotions</string> <string name="revanced_hide_get_premium_title">Hide YouTube Premium promotions</string>
@ -393,11 +397,6 @@
<string name="revanced_hide_playlist_button_title">Hide Save to playlist</string> <string name="revanced_hide_playlist_button_title">Hide Save to playlist</string>
<string name="revanced_hide_playlist_button_summary_on">Save to playlist button is hidden</string> <string name="revanced_hide_playlist_button_summary_on">Save to playlist button is hidden</string>
<string name="revanced_hide_playlist_button_summary_off">Save to playlist button is shown</string> <string name="revanced_hide_playlist_button_summary_off">Save to playlist button is shown</string>
<!-- 'Shop' should be translated with the same localized wording that YouTube displays.
Shop button appears only for some videos in certain regions. Translate the button name normally if this menu is never shown. -->
<string name="revanced_hide_shop_button_title">Hide Shop</string>
<string name="revanced_hide_shop_button_summary_on">Shop button is hidden</string>
<string name="revanced_hide_shop_button_summary_off">Shop button is shown</string>
</patch> </patch>
<patch id="layout.buttons.autoplay.HideAutoplayButtonPatch"> <patch id="layout.buttons.autoplay.HideAutoplayButtonPatch">
<string name="revanced_hide_autoplay_button_title">Hide autoplay button</string> <string name="revanced_hide_autoplay_button_title">Hide autoplay button</string>
@ -436,8 +435,11 @@
<string name="revanced_hide_subscriptions_button_summary_off">Subscriptions button is shown</string> <string name="revanced_hide_subscriptions_button_summary_off">Subscriptions button is shown</string>
<!-- 'Notifications' should be translated using the same localized wording YouTube displays the tab. --> <!-- 'Notifications' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_switch_create_with_notifications_button_title">Switch Create with Notifications</string> <string name="revanced_switch_create_with_notifications_button_title">Switch Create with Notifications</string>
<string name="revanced_switch_create_with_notifications_button_summary_on">Create button is switched with Notifications button</string> <string name="revanced_switch_create_with_notifications_button_summary_on">Create button is switched with Notifications button\n\nNote: Enabling this also forcibly hides video ads</string>
<string name="revanced_switch_create_with_notifications_button_summary_off">Create button is not switched with Notifications button</string> <string name="revanced_switch_create_with_notifications_button_summary_off">Create button is not switched with Notifications button</string>
<string name="revanced_hide_navigation_button_labels_title">Hide navigation button labels</string>
<string name="revanced_hide_navigation_button_labels_summary_on">Labels are hidden</string>
<string name="revanced_hide_navigation_button_labels_summary_off">Labels are shown</string>
</patch> </patch>
<patch id="layout.hide.player.flyoutmenupanel.HidePlayerFlyoutMenuPatch"> <patch id="layout.hide.player.flyoutmenupanel.HidePlayerFlyoutMenuPatch">
<string name="revanced_hide_player_flyout_title">Flyout menu</string> <string name="revanced_hide_player_flyout_title">Flyout menu</string>
@ -458,11 +460,6 @@
<string name="revanced_hide_player_flyout_ambient_mode_title">Hide Ambient mode</string> <string name="revanced_hide_player_flyout_ambient_mode_title">Hide Ambient mode</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_on">Ambient mode menu is hidden</string> <string name="revanced_hide_player_flyout_ambient_mode_summary_on">Ambient mode menu is hidden</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_off">Ambient mode menu is shown</string> <string name="revanced_hide_player_flyout_ambient_mode_summary_off">Ambient mode menu is shown</string>
<!-- 'Report' should be translated using the same localized wording YouTube displays for the menu item.
This item may not appear in some regions. Translate the name normally if the menu cannot be found. -->
<string name="revanced_hide_player_flyout_report_title">Hide Report</string>
<string name="revanced_hide_player_flyout_report_summary_on">Report menu is hidden</string>
<string name="revanced_hide_player_flyout_report_summary_off">Report menu is shown</string>
<!-- 'Help & feedback' should be translated using the same localized wording YouTube displays for the menu item. --> <!-- 'Help & feedback' should be translated using the same localized wording YouTube displays for the menu item. -->
<string name="revanced_hide_player_flyout_help_title">Hide Help &amp; feedback</string> <string name="revanced_hide_player_flyout_help_title">Hide Help &amp; feedback</string>
<string name="revanced_hide_player_flyout_help_summary_on">Help &amp; feedback menu is hidden</string> <string name="revanced_hide_player_flyout_help_summary_on">Help &amp; feedback menu is hidden</string>
@ -508,6 +505,9 @@
<string name="revanced_hide_comments_section_title">Hide comments section</string> <string name="revanced_hide_comments_section_title">Hide comments section</string>
<string name="revanced_hide_comments_section_summary_on">Comment section is hidden</string> <string name="revanced_hide_comments_section_summary_on">Comment section is hidden</string>
<string name="revanced_hide_comments_section_summary_off">Comment section is shown</string> <string name="revanced_hide_comments_section_summary_off">Comment section is shown</string>
<string name="revanced_hide_comment_timestamp_and_emoji_buttons_title">Hide timestamp and emoji buttons</string>
<string name="revanced_hide_comment_timestamp_and_emoji_buttons_summary_on">Comment timestamp and emoji buttons are hidden</string>
<string name="revanced_hide_comment_timestamp_and_emoji_buttons_summary_off">Comment timestamp and emoji buttons are shown</string>
</patch> </patch>
<patch id="layout.hide.crowdfundingbox.CrowdfundingBoxResourcePatch"> <patch id="layout.hide.crowdfundingbox.CrowdfundingBoxResourcePatch">
<string name="revanced_hide_crowdfunding_box_title">Hide crowdfunding box</string> <string name="revanced_hide_crowdfunding_box_title">Hide crowdfunding box</string>
@ -586,6 +586,9 @@
<string name="revanced_hide_shorts_shop_button_title">Hide shop button</string> <string name="revanced_hide_shorts_shop_button_title">Hide shop button</string>
<string name="revanced_hide_shorts_shop_button_summary_on">Shop button is hidden</string> <string name="revanced_hide_shorts_shop_button_summary_on">Shop button is hidden</string>
<string name="revanced_hide_shorts_shop_button_summary_off">Shop button is shown</string> <string name="revanced_hide_shorts_shop_button_summary_off">Shop button is shown</string>
<string name="revanced_hide_shorts_super_thanks_button_title">Hide super thanks button</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_on">Super thanks button is hidden</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_off">Super thanks button is shown</string>
<string name="revanced_hide_shorts_tagged_products_title">Hide tagged products</string> <string name="revanced_hide_shorts_tagged_products_title">Hide tagged products</string>
<string name="revanced_hide_shorts_tagged_products_summary_on">Tagged products are hidden</string> <string name="revanced_hide_shorts_tagged_products_summary_on">Tagged products are hidden</string>
<string name="revanced_hide_shorts_tagged_products_summary_off">Tagged products are shown</string> <string name="revanced_hide_shorts_tagged_products_summary_off">Tagged products are shown</string>
@ -621,7 +624,7 @@
<string name="revanced_hide_shorts_channel_bar_title">Hide channel bar</string> <string name="revanced_hide_shorts_channel_bar_title">Hide channel bar</string>
<string name="revanced_hide_shorts_channel_bar_summary_on">Channel bar is hidden</string> <string name="revanced_hide_shorts_channel_bar_summary_on">Channel bar is hidden</string>
<string name="revanced_hide_shorts_channel_bar_summary_off">Channel bar is shown</string> <string name="revanced_hide_shorts_channel_bar_summary_off">Channel bar is shown</string>
<string name="revanced_hide_shorts_video_title_title">Hide Shorts video title</string> <string name="revanced_hide_shorts_video_title_title">Hide video title</string>
<string name="revanced_hide_shorts_video_title_summary_on">Title is hidden</string> <string name="revanced_hide_shorts_video_title_summary_on">Title is hidden</string>
<string name="revanced_hide_shorts_video_title_summary_off">Title is shown</string> <string name="revanced_hide_shorts_video_title_summary_off">Title is shown</string>
<string name="revanced_hide_shorts_sound_metadata_label_title">Hide sound metadata label</string> <string name="revanced_hide_shorts_sound_metadata_label_title">Hide sound metadata label</string>
@ -861,7 +864,7 @@
<string name="revanced_sb_new_segment_confirm_title">Are the times correct?</string> <string name="revanced_sb_new_segment_confirm_title">Are the times correct?</string>
<!-- Do not rearrange the (hour):(minute):second) time format operators here. <!-- Do not rearrange the (hour):(minute):second) time format operators here.
YT shows the same seekbar time format for all languages, and this string is confirming the segment time as it appears in the seekbar. --> YT shows the same seekbar time format for all languages, and this string is confirming the segment time as it appears in the seekbar. -->
<string name="revanced_sb_new_segment_confirm_content">The segment lasts from %1$02d:%2$02d to %3$02d:%4$02d (%5$d minutes %6$02d seconds)\nIs it ready to submit?</string> <string name="revanced_sb_new_segment_confirm_content">The segment is from\n\n%1$s\nto\n%2$s\n\n(%3$s)\n\nReady to submit?</string>
<string name="revanced_sb_new_segment_start_is_before_end">Start must be before the end</string> <string name="revanced_sb_new_segment_start_is_before_end">Start must be before the end</string>
<string name="revanced_sb_new_segment_mark_locations_first">Mark two locations on the time bar first</string> <string name="revanced_sb_new_segment_mark_locations_first">Mark two locations on the time bar first</string>
<string name="revanced_sb_new_segment_preview_segment_first">Preview the segment, and ensure it skips smoothly</string> <string name="revanced_sb_new_segment_preview_segment_first">Preview the segment, and ensure it skips smoothly</string>
@ -904,7 +907,7 @@
<string name="revanced_spoof_app_version_summary_on">Version spoofed</string> <string name="revanced_spoof_app_version_summary_on">Version spoofed</string>
<string name="revanced_spoof_app_version_summary_off">Version not spoofed</string> <string name="revanced_spoof_app_version_summary_off">Version not spoofed</string>
<string name="revanced_spoof_app_version_user_dialog_message">App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.</string> <string name="revanced_spoof_app_version_user_dialog_message">App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.</string>
<!-- It is ideal, but not required, if the text here appears alphabetically after the text used for 'revanced_spoof_app_version_title'. <!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch --> This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch -->
<string name="revanced_spoof_app_version_target_title">Spoof app version target</string> <string name="revanced_spoof_app_version_target_title">Spoof app version target</string>
<!-- 'RYD' is 'Return YouTube Dislike' --> <!-- 'RYD' is 'Return YouTube Dislike' -->
@ -1082,6 +1085,21 @@
<string name="revanced_slide_to_seek_summary_on">Slide to seek is enabled</string> <string name="revanced_slide_to_seek_summary_on">Slide to seek is enabled</string>
<string name="revanced_slide_to_seek_summary_off">Slide to seek is not enabled</string> <string name="revanced_slide_to_seek_summary_off">Slide to seek is not enabled</string>
</patch> </patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">Spoof client</string>
<string name="revanced_spoof_client_screen_summary">Spoof the client to prevent playback issues</string>
<string name="revanced_spoof_client_title">Spoof client</string>
<string name="revanced_spoof_client_summary_on">Client is spoofed</string>
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_client_spoof_title' -->
<string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
<string name="revanced_spoof_client_use_ios_summary">Spoof the client to iOS instead of an Android Testsuite</string>
<string name="revanced_spoof_client_use_ios_summary_on">Client is spoofed to an iOS client\n\nSide effects include:\n• 60 fps video may not be available\n• No HDR video\n• Some videos may not load\n• Higher video qualities may be missing</string>
<string name="revanced_spoof_client_use_ios_summary_off">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include:\n• Subtitles are missing\n• Player gestures may not work\n• Low quality Shorts seekbar thumbnails</string>
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. --> <!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch"> <patch id="misc.fix.playback.SpoofSignaturePatch">
<string name="revanced_spoof_signature_verification_screen_title">Spoof app signature</string> <string name="revanced_spoof_signature_verification_screen_title">Spoof app signature</string>
@ -1155,9 +1173,9 @@
<string name="revanced_other_category_title">Other settings</string> <string name="revanced_other_category_title">Other settings</string>
<string name="revanced_client_ads_category_title">Client-side ads</string> <string name="revanced_client_ads_category_title">Client-side ads</string>
<string name="revanced_surestream_ads_category_title">Server-side surestream ads</string> <string name="revanced_surestream_ads_category_title">Server-side surestream ads</string>
<string name="revanced_debug_title">Debug logging</string> <string name="revanced_twitch_debug_title">Debug logging</string>
<string name="revanced_debug_summary_on">Debug logs are enabled</string> <string name="revanced_twitch_debug_summary_on">Debug logs are enabled</string>
<string name="revanced_debug_summary_off">Debug logs are disabled</string> <string name="revanced_twitch_debug_summary_off">Debug logs are disabled</string>
</patch> </patch>
</app> </app>
</resources> </resources>