From 5c7c8b536416ec53cd98f7d59d11850aa1b70f11 Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Sat, 20 Sep 2025 14:36:15 +0400
Subject: [PATCH] fix(Spoof video streams): Resolve occasional playback
stuttering
Code adapted from:
https://github.com/inotia00/revanced-patches/commit/2cf9db66ac3f241d74cbbe2579961f4e7c57f8ae
https://github.com/inotia00/revanced-patches/commit/50d9c60374d8c2ee4f8034b3cbcb1d68e59b6a84
---
.../shared/spoof/SpoofVideoStreamsPatch.java | 39 +++++++-
.../patches/shared/misc/spoof/Fingerprints.kt | 19 ++--
.../misc/spoof/SpoofVideoStreamsPatch.kt | 89 +++++++++++++++++--
3 files changed, 132 insertions(+), 15 deletions(-)
diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java
index f11c7ea26..a5f69ba30 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java
@@ -97,6 +97,35 @@ public class SpoofVideoStreamsPatch {
return playerRequestUri;
}
+ /**
+ * Injection point.
+ *
+ * Blocks /get_watch requests by returning an unreachable URI.
+ * /att/get requests are used to obtain a PoToken challenge.
+ * See: botGuardScript.js#L15
+ *
+ * Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained,
+ * Blocking /att/get requests are not a problem.
+ */
+ public static String blockGetAttRequest(String originalUrlString) {
+ if (SPOOF_STREAMING_DATA) {
+ try {
+ var originalUri = Uri.parse(originalUrlString);
+ String path = originalUri.getPath();
+
+ if (path != null && path.contains("att/get")) {
+ Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri");
+
+ return INTERNET_CONNECTION_CHECK_URI_STRING;
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "blockGetAttRequest failure", ex);
+ }
+ }
+
+ return originalUrlString;
+ }
+
/**
* Injection point.
*
@@ -130,7 +159,7 @@ public class SpoofVideoStreamsPatch {
/**
* Injection point.
- * Only invoked when playing a livestream on an iOS client.
+ * Only invoked when playing a livestream on an Apple client.
*/
public static boolean fixHLSCurrentTime(boolean original) {
if (!SPOOF_STREAMING_DATA) {
@@ -139,6 +168,14 @@ public class SpoofVideoStreamsPatch {
return false;
}
+ /*
+ * Injection point.
+ * Fix audio stuttering in YouTube Music.
+ */
+ public static boolean disableSABR() {
+ return SPOOF_STREAMING_DATA;
+ }
+
/**
* Injection point.
* Turns off a feature flag that interferes with spoofing.
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
index ed2f86387..7c42648c7 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
@@ -1,12 +1,9 @@
package app.revanced.patches.shared.misc.spoof
import app.revanced.patcher.fingerprint
-import app.revanced.util.getReference
-import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
-import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val buildInitPlaybackRequestFingerprint = fingerprint {
returns("Lorg/chromium/net/UrlRequest\$Builder;")
@@ -40,10 +37,7 @@ internal val buildRequestFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
custom { methodDef, _ ->
- if (methodDef.indexOfFirstInstruction {
- val reference = getReference()
- reference?.name == "newUrlRequestBuilder"
- } < 0) {
+ if (indexOfNewUrlRequestBuilderInstruction(methodDef) < 0) {
return@custom false
}
@@ -142,6 +136,17 @@ internal val hlsCurrentTimeFingerprint = fingerprint {
}
}
+internal const val DISABLED_BY_SABR_STREAMING_URI_STRING = "DISABLED_BY_SABR_STREAMING_URI"
+
+internal val mediaFetchEnumConstructorFingerprint = fingerprint {
+ returns("V")
+ strings(
+ "ENABLED",
+ "DISABLED_FOR_PLAYBACK",
+ DISABLED_BY_SABR_STREAMING_URI_STRING
+ )
+}
+
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/String;")
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
index 26b80a2f9..40746553f 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
@@ -5,23 +5,28 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
+import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.util.findFreeRegister
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
import app.revanced.util.returnEarly
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.Method
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.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -31,6 +36,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/shared/spoof/SpoofVideoStreamsPatch;"
+private lateinit var buildRequestMethod: MutableMethod
+private var buildRequestMethodUrlRegister = -1
+
fun spoofVideoStreamsPatch(
block: BytecodePatchBuilder.() -> Unit = {},
fixMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
@@ -91,18 +99,17 @@ fun spoofVideoStreamsPatch(
// region Get replacement streams at player requests.
buildRequestFingerprint.method.apply {
- val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
- opcode == Opcode.INVOKE_VIRTUAL &&
- getReference()?.name == "newUrlRequestBuilder"
- }
- val urlRegister = getInstruction(newRequestBuilderIndex).registerD
- val freeRegister = findFreeRegister(newRequestBuilderIndex, urlRegister)
+ buildRequestMethod = this
+
+ val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
+ buildRequestMethodUrlRegister = getInstruction(newRequestBuilderIndex).registerD
+ val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodUrlRegister)
addInstructions(
newRequestBuilderIndex,
"""
move-object v$freeRegister, p1
- invoke-static { v$urlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
+ invoke-static { v$buildRequestMethodUrlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
"""
)
}
@@ -187,6 +194,21 @@ fun spoofVideoStreamsPatch(
// endregion
+ // region block getAtt request
+
+ buildRequestMethod.apply {
+ val insertIndex = indexOfNewUrlRequestBuilderInstruction(this)
+
+ addInstructions(
+ insertIndex, """
+ invoke-static { v$buildRequestMethodUrlRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v$buildRequestMethodUrlRegister
+ """
+ )
+ }
+
+ // endregion
+
// region Remove /videoplayback request body to fix playback.
// It is assumed, YouTube makes a request with a body tuned for Android.
// Requesting streams intended for other platforms with a body tuned for Android could be the cause of 400 errors.
@@ -243,6 +265,50 @@ fun spoofVideoStreamsPatch(
// endregion
+ // region Disable SABR playback.
+ // If SABR is disabled, it seems 'MediaFetchHotConfig' may no longer need an override (not confirmed).
+
+ val (mediaFetchEnumClass, sabrFieldReference) = with(mediaFetchEnumConstructorFingerprint.method) {
+ val stringIndex = mediaFetchEnumConstructorFingerprint.stringMatches!!.first {
+ it.string == DISABLED_BY_SABR_STREAMING_URI_STRING
+ }.index
+
+ val mediaFetchEnumClass = definingClass
+ val sabrFieldIndex = indexOfFirstInstructionOrThrow(stringIndex) {
+ opcode == Opcode.SPUT_OBJECT &&
+ getReference()?.type == mediaFetchEnumClass
+ }
+
+ Pair(
+ mediaFetchEnumClass,
+ getInstruction(sabrFieldIndex).reference
+ )
+ }
+
+ fingerprint {
+ returns(mediaFetchEnumClass)
+ opcodes(
+ Opcode.SGET_OBJECT,
+ Opcode.RETURN_OBJECT,
+ )
+ custom { method, _ ->
+ !method.parameterTypes.isEmpty()
+ }
+ }.method.addInstructionsWithLabels(
+ 0,
+ """
+ invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z
+ move-result v0
+ if-eqz v0, :ignore
+ sget-object v0, $sabrFieldReference
+ return-object v0
+ :ignore
+ nop
+ """
+ )
+
+ // endregion
+
// region turn off stream config replacement feature flag.
if (fixMediaFetchHotConfigChanges()) {
@@ -271,3 +337,12 @@ fun spoofVideoStreamsPatch(
executeBlock()
}
}
+
+internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.indexOfFirstInstruction {
+ opcode == Opcode.INVOKE_VIRTUAL && getReference().toString() ==
+ "Lorg/chromium/net/CronetEngine;" +
+ "->newUrlRequestBuilder(" +
+ "Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;" +
+ "Ljava/util/concurrent/Executor;" +
+ ")Lorg/chromium/net/UrlRequest${'$'}Builder;"
+}