fix(YouTube - Spoof video streams): Resolve playback issues with dynamic player config (#4521)

This commit is contained in:
LisoUseInAIKyrios 2025-03-02 17:38:43 +02:00 committed by GitHub
parent 52e4b9c43e
commit 647e7642ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 85 additions and 47 deletions

View File

@ -362,27 +362,18 @@ public class Utils {
} }
public static void setContext(Context appContext) { public static void setContext(Context appContext) {
// Must initially set context as the language settings needs it. // Must initially set context to check the app language.
context = appContext; context = appContext;
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language != AppLanguage.DEFAULT) { if (language != AppLanguage.DEFAULT) {
// Create a new context with the desired language. // Create a new context with the desired language.
Logger.printDebug(() -> "Using app language: " + language);
Configuration config = appContext.getResources().getConfiguration(); Configuration config = appContext.getResources().getConfiguration();
config.setLocale(language.getLocale()); config.setLocale(language.getLocale());
context = appContext.createConfigurationContext(config); context = appContext.createConfigurationContext(config);
} }
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
// even though the context is already set before the call.
//
// The initialization logger methods do not directly or indirectly
// reference the Context or any Settings and are unaffected by this problem.
//
// Info level also helps debug if a patch hook is called before
// the context is set since debug logging is off by default.
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
} }
public static void setClipboard(@NonNull String text) { public static void setClipboard(@NonNull String text) {

View File

@ -107,6 +107,21 @@ public class SpoofVideoStreamsPatch {
return false; return false;
} }
/**
* Injection point.
* Turns off a feature flag that interferes with spoofing.
*/
public static boolean useMediaFetchHotConfigReplacement(boolean original) {
if (original) {
Logger.printDebug(() -> "useMediaFetchHotConfigReplacement is set on");
}
if (!SPOOF_STREAMING_DATA) {
return original;
}
return false;
}
/** /**
* Injection point. * Injection point.
*/ */

View File

@ -24,7 +24,7 @@ public final class EnableDebuggingPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) { public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
if (LOG_FEATURE_FLAGS && value) { if (LOG_FEATURE_FLAGS && value) {
if (featureFlags.putIfAbsent(flag, true) == null) { if (featureFlags.putIfAbsent(flag, true) == null) {
Logger.printDebug(() -> "boolean feature is enabled: " + flag); Logger.printDebug(() -> "boolean feature is enabled: " + flag);

View File

@ -776,8 +776,8 @@ public final class app/revanced/patches/shared/misc/settings/preference/TextPref
} }
public final class app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatchKt { public final class app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatchKt {
public static final fun spoofVideoStreamsPatch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun spoofVideoStreamsPatch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch; public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatchKt { public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatchKt {

View File

@ -137,3 +137,15 @@ internal val patchIncludedExtensionMethodFingerprint = fingerprint {
classDef.type == EXTENSION_CLASS_DESCRIPTOR && method.name == "isPatchIncluded" classDef.type == EXTENSION_CLASS_DESCRIPTOR && method.name == "isPatchIncluded"
} }
} }
// Feature flag that turns on Platypus programming language code compiled to native C++.
// This code appears to replace the player config after the streams are loaded.
// Flag is present in YouTube 19.34, but is missing Platypus stream replacement code until 19.43.
// Flag and Platypus code is also present in newer versions of YouTube Music.
internal const val MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG = 45645570L
internal val mediaFetchHotConfigFingerprint = fingerprint {
literal {
MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG
}
}

View File

@ -31,6 +31,7 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
fun spoofVideoStreamsPatch( fun spoofVideoStreamsPatch(
block: BytecodePatchBuilder.() -> Unit = {}, block: BytecodePatchBuilder.() -> Unit = {},
applyMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
executeBlock: BytecodePatchContext.() -> Unit = {}, executeBlock: BytecodePatchContext.() -> Unit = {},
) = bytecodePatch( ) = bytecodePatch(
name = "Spoof video streams", name = "Spoof video streams",
@ -238,6 +239,17 @@ fun spoofVideoStreamsPatch(
// endregion // endregion
// region turn off stream config replacement feature flag.
if (applyMediaFetchHotConfigChanges()) {
mediaFetchHotConfigFingerprint.method.insertFeatureFlagBooleanOverride(
MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z"
)
}
// endregion
executeBlock() executeBlock()
} }
} }

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.debugging package app.revanced.patches.youtube.misc.debugging
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.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
@ -11,9 +12,11 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;" "Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;"
@ -61,20 +64,18 @@ val enableDebuggingPatch = bytecodePatch(
experimentalBooleanFeatureFlagFingerprint.match( experimentalBooleanFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply { ).method.apply {
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
// It appears that all usage of this method has a default of 'false',
// so there's no need to pass in the default.
addInstructions( addInstructions(
insertIndex, index,
""" """
move-result v0 invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z
invoke-static { v0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZJ)Z move-result v$register
move-result v0
return v0
""" """
) )
} }
}
experimentalDoubleFeatureFlagFingerprint.match( experimentalDoubleFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef experimentalFeatureFlagParentFingerprint.originalClassDef
@ -92,7 +93,6 @@ val enableDebuggingPatch = bytecodePatch(
) )
} }
experimentalLongFeatureFlagFingerprint.match( experimentalLongFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply { ).method.apply {
@ -108,6 +108,8 @@ val enableDebuggingPatch = bytecodePatch(
""" """
) )
}
experimentalStringFeatureFlagFingerprint.match( experimentalStringFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply { ).method.apply {
@ -123,7 +125,6 @@ val enableDebuggingPatch = bytecodePatch(
""" """
) )
} }
}
// There exists other experimental accessor methods for byte[] // There exists other experimental accessor methods for byte[]
// and wrappers for obfuscated classes, but currently none of those are hooked. // and wrappers for obfuscated classes, but currently none of those are hooked.

View File

@ -11,9 +11,9 @@ internal val experimentalFeatureFlagParentFingerprint = fingerprint {
} }
internal val experimentalBooleanFeatureFlagFingerprint = fingerprint { internal val experimentalBooleanFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Z") returns("Z")
parameters("J", "Z") parameters("L", "J", "Z")
} }
internal val experimentalDoubleFeatureFlagFingerprint = fingerprint { internal val experimentalDoubleFeatureFlagFingerprint = fingerprint {
@ -33,4 +33,3 @@ internal val experimentalStringFeatureFlagFingerprint = fingerprint {
returns("Ljava/lang/String;") returns("Ljava/lang/String;")
parameters("J", "Ljava/lang/String;") parameters("J", "Ljava/lang/String;")
} }

View File

@ -6,6 +6,8 @@ import app.revanced.patches.shared.misc.settings.preference.NonInteractivePrefer
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
@ -25,7 +27,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
dependsOn( dependsOn(
userAgentClientSpoofPatch, userAgentClientSpoofPatch,
settingsPatch, settingsPatch,
versionCheckPatch
) )
}, {
is_19_34_or_greater
}, { }, {
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch") addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")

View File

@ -408,10 +408,13 @@ internal fun MutableMethod.insertFeatureFlagBooleanOverride(literal: Long, exten
val index = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) val index = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(index).registerA val register = getInstruction<OneRegisterInstruction>(index).registerA
val operation = if (register < 16) "invoke-static { v$register }"
else "invoke-static/range { v$register .. v$register }"
addInstructions( addInstructions(
index + 1, index + 1,
""" """
invoke-static { v$register }, $extensionsMethod $operation, $extensionsMethod
move-result v$register move-result v$register
""" """
) )
@ -458,7 +461,7 @@ fun MutableMethod.returnEarly(bool: Boolean = false) {
return v0 return v0
""" """
else -> throw Exception("This case should never happen.") else -> throw Exception("Return type is not supported: $this")
} }
addInstructions(0, stringInstructions) addInstructions(0, stringInstructions)