chore: Merge branch dev to main (#3234)

This commit is contained in:
oSumAtrIX 2024-05-31 12:04:02 +02:00 committed by GitHub
commit 956a9e9336
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 2373 additions and 1082 deletions

View file

@ -70,7 +70,7 @@ body:
Before creating a new bug report, please keep the following in mind: Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-patches/labels/Bug%20report). - **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md). - **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea - type: textarea
@ -102,7 +102,7 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below. description: Your bug report will be closed if you don't follow the checklist below.
options: options:
- label: This issue is not a duplicate of an existing bug report. - label: I have checked all open and closed bug reports and this is not a duplicate.
required: true required: true
- label: I have chosen an appropriate title. - label: I have chosen an appropriate title.
required: true required: true

View file

@ -70,7 +70,7 @@ body:
Before creating a new feature request, please keep the following in mind: Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-patches/labels/Feature%20request). - **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md). - **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea - type: textarea
@ -98,7 +98,7 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below. description: Your feature request will be closed if you don't follow the checklist below.
options: options:
- label: This issue is not a duplicate of an existing feature request. - label: I have checked all open and closed feature requests and this is not a duplicate
required: true required: true
- label: I have chosen an appropriate title. - label: I have chosen an appropriate title.
required: true required: true

View file

@ -1,3 +1,31 @@
## [4.8.3-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.8.3-dev.3...v4.8.3-dev.4) (2024-05-30)
### Bug Fixes
* **3rd-party Reddit apps:** Spoof user agent to work around Reddit API issues ([#3253](https://github.com/ReVanced/revanced-patches/issues/3253)) ([495e6d6](https://github.com/ReVanced/revanced-patches/commit/495e6d65e7cbae88baa71f8334b9afcf9819deaf))
## [4.8.3-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.8.3-dev.2...v4.8.3-dev.3) (2024-05-30)
### Bug Fixes
* **Reddit - Hide ads:** Constrain to last working version 2024.17.0 ([#3192](https://github.com/ReVanced/revanced-patches/issues/3192)) ([4fb3456](https://github.com/ReVanced/revanced-patches/commit/4fb3456e93ff7ec19030de8870e1cb9c1319faef))
## [4.8.3-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.8.3-dev.1...v4.8.3-dev.2) (2024-05-30)
### Bug Fixes
* **YouTube - Spoof client:** Clarify that only enter/exit fullscreen gesture does not work with Android VR spoof ([#3243](https://github.com/ReVanced/revanced-patches/issues/3243)) ([06d8f55](https://github.com/ReVanced/revanced-patches/commit/06d8f55e9b6ce27d8c550f202615689ac9c34cfa))
## [4.8.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.8.2...v4.8.3-dev.1) (2024-05-25)
### Bug Fixes
* **YouTube - Spoof client:** Improve Android spoofing ([#3230](https://github.com/ReVanced/revanced-patches/issues/3230)) ([b688923](https://github.com/ReVanced/revanced-patches/commit/b688923c7e83805f2377a19b20a969b8cb749a9c))
## [4.8.2](https://github.com/ReVanced/revanced-patches/compare/v4.8.1...v4.8.2) (2024-05-24) ## [4.8.2](https://github.com/ReVanced/revanced-patches/compare/v4.8.1...v4.8.2) (2024-05-24)

View file

@ -555,6 +555,7 @@ public final class app/revanced/patches/reddit/customclients/baconreader/api/Spo
public final class app/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch { public final class app/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch; public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/boostforreddit/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 patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
} }
public final class app/revanced/patches/reddit/customclients/infinityforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch { public final class app/revanced/patches/reddit/customclients/infinityforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
@ -577,6 +578,11 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/ads/D
public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch { public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch; public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/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 patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
}
public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/fingerprints/AuthUtilityUserAgent : app/revanced/patcher/fingerprint/MethodFingerprint {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/api/fingerprints/AuthUtilityUserAgent;
} }
public final class app/revanced/patches/reddit/customclients/joeyforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/reddit/customclients/joeyforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {
@ -621,6 +627,7 @@ public final class app/revanced/patches/reddit/customclients/syncforreddit/api/S
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/api/SpoofClientPatch; public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/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 patchMiscellaneous (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
} }
public final class app/revanced/patches/reddit/customclients/syncforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/reddit/customclients/syncforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {

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.8.2 version = 4.8.3-dev.4

View file

@ -1,10 +1,11 @@
[versions] [versions]
revanced-patcher = "19.3.1" revanced-patcher = "19.3.1"
smali = "3.0.5" #noinspection GradleDependency
guava = "33.0.0-jre" smali = "3.0.5" # 3.0.7 breaks binary compatibility. Tracking https://github.com/google/smali/issues/58.
guava = "33.1.0-jre"
gson = "2.10.1" gson = "2.10.1"
binary-compatibility-validator = "0.14.0" binary-compatibility-validator = "0.14.0"
kotlin = "1.9.22" kotlin = "2.0.0"
[libraries] [libraries]
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }

View file

@ -1,6 +1,8 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dist zipStorePath=wrapper/dists

20
gradlew.bat vendored
View file

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

3059
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1", "gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.2" "semantic-release": "^23.0.8"
} }
} }

View file

@ -4,6 +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
// Note that for now, this patch and anything using it will only work on
// Reddit 2024.17.0 or older. Newer versions will crash during patching.
// See https://github.com/ReVanced/revanced-patches/issues/3099
@Patch(description = "Hides banner ads from comments on subreddits.") @Patch(description = "Hides banner ads from comments on subreddits.")
object HideBannerPatch : ResourcePatch() { object HideBannerPatch : ResourcePatch() {
private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml" private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml"

View file

@ -20,7 +20,12 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( @Patch(
name = "Hide ads", name = "Hide ads",
dependencies = [HideBannerPatch::class, HideCommentAdsPatch::class], dependencies = [HideBannerPatch::class, HideCommentAdsPatch::class],
compatiblePackages = [CompatiblePackage("com.reddit.frontpage")], // Note that for now, this patch and anything using it will only work on
// Reddit 2024.17.0 or older. Newer versions will crash during patching.
// See https://github.com/ReVanced/revanced-patches/issues/3099
// and https://github.com/iBotPeaches/Apktool/issues/3534.
// This constraint is necessary due to dependency on HideBannerPatch.
compatiblePackages = [CompatiblePackage("com.reddit.frontpage", ["2024.17.0"])],
requiresIntegrations = true, requiresIntegrations = true,
) )
@Suppress("unused") @Suppress("unused")

View file

@ -4,16 +4,15 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.BuildUserAgentFingerprint
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.GetClientIdFingerprint import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.GetClientIdFingerprint
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.LoginActivityOnCreateFingerprint
@Suppress("unused") @Suppress("unused")
object SpoofClientPatch : BaseSpoofClientPatch( object SpoofClientPatch : BaseSpoofClientPatch(
redirectUri = "http://rubenmayayo.com", redirectUri = "http://rubenmayayo.com",
clientIdFingerprints = setOf(GetClientIdFingerprint), clientIdFingerprints = setOf(GetClientIdFingerprint),
userAgentFingerprints = setOf(LoginActivityOnCreateFingerprint), userAgentFingerprints = setOf(BuildUserAgentFingerprint),
compatiblePackages = setOf(CompatiblePackage("com.rubenmayayo.reddit")) compatiblePackages = setOf(CompatiblePackage("com.rubenmayayo.reddit")),
) { ) {
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) { override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
first().mutableMethod.addInstructions( first().mutableMethod.addInstructions(
@ -21,7 +20,18 @@ object SpoofClientPatch : BaseSpoofClientPatch(
""" """
const-string v0, "$clientId" const-string v0, "$clientId"
return-object v0 return-object v0
""" """,
)
}
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
// Use a random number as the platform in the user agent string.
val platformName = (0..100000).random()
val platformParameter = 0
first().mutableMethod.addInstructions(
0,
"const-string p$platformParameter, \"$platformName\"",
) )
} }
} }

View file

@ -0,0 +1,7 @@
package app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object BuildUserAgentFingerprint : MethodFingerprint(
strings = listOf("%s:%s:%s (by /u/%s)"),
)

View file

@ -1,14 +0,0 @@
package app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object LoginActivityOnCreateFingerprint : MethodFingerprint(
opcodes = listOf(
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4
),
customFingerprint = { method, classDef ->
method.name == "onCreate" && classDef.type.endsWith("LoginActivity;")
}
)

View file

@ -2,8 +2,10 @@ package app.revanced.patches.reddit.customclients.joeyforreddit.api
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.AuthUtilityUserAgent
import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.GetClientIdFingerprint import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.GetClientIdFingerprint
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.DisablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.DisablePiracyDetectionPatch
@ -12,6 +14,7 @@ import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.
object SpoofClientPatch : BaseSpoofClientPatch( object SpoofClientPatch : BaseSpoofClientPatch(
redirectUri = "https://127.0.0.1:65023/authorize_callback", redirectUri = "https://127.0.0.1:65023/authorize_callback",
clientIdFingerprints = setOf(GetClientIdFingerprint), clientIdFingerprints = setOf(GetClientIdFingerprint),
userAgentFingerprints = setOf(AuthUtilityUserAgent),
compatiblePackages = setOf( compatiblePackages = setOf(
CompatiblePackage("o.o.joey"), CompatiblePackage("o.o.joey"),
CompatiblePackage("o.o.joey.pro"), CompatiblePackage("o.o.joey.pro"),
@ -28,4 +31,18 @@ object SpoofClientPatch : BaseSpoofClientPatch(
""" """
) )
} }
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
// Use a random user agent.
val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
first().mutableMethod.replaceInstructions(
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
}
} }

View file

@ -0,0 +1,15 @@
package app.revanced.patches.reddit.customclients.joeyforreddit.api.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
object AuthUtilityUserAgent : MethodFingerprint(
returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
opcodes = listOf(Opcode.APUT_OBJECT),
customFingerprint = { _, classDef ->
classDef.sourceFile == "AuthUtility.java"
},
)

View file

@ -1,18 +1,16 @@
package app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy package app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy
import app.revanced.util.exception
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.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.fingerprints.PiracyDetectionFingerprint import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.fingerprints.PiracyDetectionFingerprint
import app.revanced.util.exception
object DisablePiracyDetectionPatch : BytecodePatch(setOf(PiracyDetectionFingerprint)) { object DisablePiracyDetectionPatch : BytecodePatch(setOf(PiracyDetectionFingerprint)) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
PiracyDetectionFingerprint.result?.mutableMethod?.addInstruction( PiracyDetectionFingerprint.result?.mutableMethod?.addInstruction(
0, 0,
""" "return-void",
return-void
"""
) ?: throw PiracyDetectionFingerprint.exception ) ?: throw PiracyDetectionFingerprint.exception
} }
} }

View file

@ -14,7 +14,6 @@ import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction 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 import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Suppress("unused") @Suppress("unused")
object SpoofClientPatch : BaseSpoofClientPatch( object SpoofClientPatch : BaseSpoofClientPatch(
redirectUri = "redditisfun://auth", redirectUri = "redditisfun://auth",
@ -54,7 +53,7 @@ object SpoofClientPatch : BaseSpoofClientPatch(
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) { override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
// Use a random user agent. // Use a random user agent.
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "android:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
first().mutableMethod.addInstructions( first().mutableMethod.addInstructions(
0, 0,

View file

@ -6,10 +6,10 @@ 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.fingerprint.MethodFingerprintResult import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.*
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetAuthorizationStringFingerprint import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetAuthorizationStringFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetBearerTokenFingerprint import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetBearerTokenFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.ImgurImageAPIFingerprint import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.ImgurImageAPIFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.LoadBrowserURLFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.detection.piracy.DisablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.syncforreddit.detection.piracy.DisablePiracyDetectionPatch
import app.revanced.util.exception import app.revanced.util.exception
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@ -17,19 +17,18 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import java.util.* import java.util.*
@Suppress("unused") @Suppress("unused")
object SpoofClientPatch : BaseSpoofClientPatch( object SpoofClientPatch : BaseSpoofClientPatch(
redirectUri = "http://redditsync/auth", redirectUri = "http://redditsync/auth",
miscellaneousFingerprints = setOf(ImgurImageAPIFingerprint), miscellaneousFingerprints = setOf(ImgurImageAPIFingerprint),
clientIdFingerprints = setOf(GetAuthorizationStringFingerprint), clientIdFingerprints = setOf(GetAuthorizationStringFingerprint),
userAgentFingerprints = setOf(LoadBrowserURLFingerprint), userAgentFingerprints = setOf(GetUserAgentFingerprint),
compatiblePackages = setOf( compatiblePackages = setOf(
CompatiblePackage("com.laurencedawson.reddit_sync"), CompatiblePackage("com.laurencedawson.reddit_sync"),
CompatiblePackage("com.laurencedawson.reddit_sync.pro"), CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
CompatiblePackage("com.laurencedawson.reddit_sync.dev") CompatiblePackage("com.laurencedawson.reddit_sync.dev"),
), ),
dependencies = setOf(DisablePiracyDetectionPatch::class) dependencies = setOf(DisablePiracyDetectionPatch::class),
) { ) {
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) { override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
forEach { fingerprintResult -> forEach { fingerprintResult ->
@ -41,7 +40,7 @@ object SpoofClientPatch : BaseSpoofClientPatch(
""" """
const-string v0, "Basic $auth" const-string v0, "Basic $auth"
return-object v0 return-object v0
""" """,
) )
} ?: throw GetBearerTokenFingerprint.exception } ?: throw GetBearerTokenFingerprint.exception
}.let { }.let {
@ -54,12 +53,12 @@ object SpoofClientPatch : BaseSpoofClientPatch(
val newAuthorizationUrl = reference.string.replace( val newAuthorizationUrl = reference.string.replace(
"client_id=.*?&".toRegex(), "client_id=.*?&".toRegex(),
"client_id=$clientId&" "client_id=$clientId&",
) )
replaceInstruction( replaceInstruction(
occurrenceIndex, occurrenceIndex,
"const-string v$targetRegister, \"$newAuthorizationUrl\"" "const-string v$targetRegister, \"$newAuthorizationUrl\"",
) )
} }
} }
@ -72,7 +71,21 @@ object SpoofClientPatch : BaseSpoofClientPatch(
it.mutableMethod.replaceInstruction( it.mutableMethod.replaceInstruction(
apiUrlIndex, apiUrlIndex,
"const-string v1, \"https://api.imgur.com/3/image\"" "const-string v1, \"https://api.imgur.com/3/image\"",
)
}
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
// Use a random user agent.
val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
first().mutableMethod.replaceInstruction(
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
) )
} }
} }

View file

@ -0,0 +1,7 @@
package app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object GetUserAgentFingerprint : MethodFingerprint(
strings = listOf("android:com.laurencedawson.reddit_sync"),
)

View file

@ -15,11 +15,13 @@ 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
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.* import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.SetPlayerRequestClientTypeFingerprint
import app.revanced.patches.youtube.misc.settings.SettingsPatch 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.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
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
@ -35,11 +37,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
name = "Spoof client", name = "Spoof client",
description = "Spoofs the client to allow video playback.", description = "Spoofs the client to allow video playback.",
dependencies = [ dependencies = [
PlayerResponseMethodHookPatch::class,
SettingsPatch::class, SettingsPatch::class,
AddResourcesPatch::class, AddResourcesPatch::class,
UserAgentClientSpoofPatch::class, UserAgentClientSpoofPatch::class,
PlayerResponseMethodHookPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
@ -69,19 +69,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
) )
object SpoofClientPatch : BytecodePatch( object SpoofClientPatch : BytecodePatch(
setOf( setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint, BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint, BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint, SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint, CreatePlayerRequestBodyFingerprint,
CreatePlayerRequestBodyWithModelFingerprint, CreatePlayerRequestBodyWithModelFingerprint,
// Storyboard spoof.
StoryboardRendererSpecFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
PlayerResponseModelImplGeneralFingerprint,
StoryboardRendererDecoderSpecFingerprint,
), ),
) { ) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR = private const val INTEGRATIONS_CLASS_DESCRIPTOR =
@ -98,7 +90,7 @@ object SpoofClientPatch : BytecodePatch(
sorting = Sorting.UNSORTED, sorting = Sorting.UNSORTED,
preferences = setOf( preferences = setOf(
SwitchPreference("revanced_spoof_client"), SwitchPreference("revanced_spoof_client"),
SwitchPreference("revanced_spoof_client_use_testsuite"), SwitchPreference("revanced_spoof_client_use_ios"),
), ),
), ),
) )
@ -149,11 +141,11 @@ object SpoofClientPatch : BytecodePatch(
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result -> SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object. // Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod val clientInfoField = result.mutableMethod
.getInstructions().first { instruction -> .getInstructions().find { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build(); // requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT && instruction.opcode == Opcode.IPUT_OBJECT &&
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField") }?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
// Client info object's client type field. // Client info object's client type field.
val clientInfoClientTypeField = result.mutableMethod val clientInfoClientTypeField = result.mutableMethod
@ -168,20 +160,17 @@ object SpoofClientPatch : BytecodePatch(
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
} }
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let { val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
val instructions = it.getInstructions() val getClientModelIndex = CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
val instructions = it.mutableMethod.getInstructions()
val getClientModelIndex = it.indexOfFirstInstruction {
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
}
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field. // The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
instructions.subList( instructions.subList(
getClientModelIndex, getClientModelIndex,
instructions.lastIndex, instructions.size,
).first { instruction -> ).find { instruction ->
instruction.opcode == Opcode.IPUT_OBJECT instruction.opcode == Opcode.IPUT_OBJECT
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField") }?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
} }
// endregion // endregion
@ -243,6 +232,7 @@ object SpoofClientPatch : BytecodePatch(
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String; invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1 move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField iput-object v1, v0, $clientInfoClientVersionField
:disabled :disabled
return-void return-void
""", """,
@ -253,104 +243,5 @@ object SpoofClientPatch : BytecodePatch(
// endregion // 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

@ -2,8 +2,12 @@ package app.revanced.patches.youtube.misc.fix.playback.fingerprints
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.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
import app.revanced.util.containsWideLiteralInstructionValue
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint( internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
@ -11,8 +15,17 @@ internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(), parameters = listOf(),
customFingerprint = { methodDef, _ -> customFingerprint = { methodDef, _ ->
methodDef.implementation!!.instructions.any { methodDef.containsWideLiteralInstructionValue(1073741824) &&
it.getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;" indexOfBuildModelInstruction(methodDef) >= 0
}
}, },
) ) {
fun indexOfBuildModelInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build;" &&
reference.name == "MODEL" &&
reference.type == "Ljava/lang/String;"
}
}

View file

@ -6,6 +6,7 @@ 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
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint( internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;", returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -6,6 +6,7 @@ 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
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint( internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
returnType = "I", returnType = "I",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

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

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint]. * Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint( internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint]. * Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint( internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererSpecFingerprint : MethodFingerprint( internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "L", returnType = "L",

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves using the class found in [StoryboardThumbnailParentFingerprint]. * Resolves using the class found in [StoryboardThumbnailParentFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardThumbnailFingerprint : MethodFingerprint( internal object StoryboardThumbnailFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z", returnType = "Z",

View file

@ -1092,10 +1092,9 @@
<string name="revanced_spoof_client_summary_on">Client is spoofed</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_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> <string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
<string name="revanced_spoof_client_use_testsuite_title">Spoof client to Android Testsuite</string> <string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
<string name="revanced_spoof_client_use_testsuite_summary">Spoof the client to Android Testsuite</string> <string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Speed menu is missing\n• Watch history may not work\n• Live streams cannot play as audio only\n• Live streams not available on older devices</string>
<string name="revanced_spoof_client_use_testsuite_summary_on">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include, but are not limited to:\n• Speed flyout menu is missing\n• Captions are missing\n• Player swipe gestures may not work\n• Low quality Shorts seekbar thumbnails\n• Watch history may not work</string> <string name="revanced_spoof_client_use_ios_summary_off">Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Swipe to enter/exit fullscreen does not work\n• Kids videos do not playback\n• Paused videos can randomly resume</string>
<string name="revanced_spoof_client_use_testsuite_summary_off">Client is spoofed to an iOS client\n\nSide effects include:\n• No HDR video\n• Speed flyout menu is missing</string>
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</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> <string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
</patch> </patch>