diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index c986c6566..47806567c 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -362,27 +362,18 @@ public class Utils { } 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; + Logger.initializationInfo(Utils.class, "Set context: " + appContext); AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); if (language != AppLanguage.DEFAULT) { // Create a new context with the desired language. + Logger.printDebug(() -> "Using app language: " + language); Configuration config = appContext.getResources().getConfiguration(); config.setLocale(language.getLocale()); 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) { 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 225f5c6b9..07af5ed43 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 @@ -107,6 +107,21 @@ public class SpoofVideoStreamsPatch { 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. */ diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java index f0e0e1f27..ff891edc1 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java @@ -24,7 +24,7 @@ public final class EnableDebuggingPatch { /** * Injection point. */ - public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) { + public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) { if (LOG_FEATURE_FLAGS && value) { if (featureFlags.putIfAbsent(flag, true) == null) { Logger.printDebug(() -> "boolean feature is enabled: " + flag); diff --git a/patches/api/patches.api b/patches/api/patches.api index ef2702984..94aee7801 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -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 static final fun spoofVideoStreamsPatch (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 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;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch; } public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatchKt { 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 164d67b1a..8cd04d0af 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 @@ -137,3 +137,15 @@ internal val patchIncludedExtensionMethodFingerprint = fingerprint { 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 + } +} 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 c6628f5cb..4b51c50ec 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 @@ -31,6 +31,7 @@ internal const val EXTENSION_CLASS_DESCRIPTOR = fun spoofVideoStreamsPatch( block: BytecodePatchBuilder.() -> Unit = {}, + applyMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false }, executeBlock: BytecodePatchContext.() -> Unit = {}, ) = bytecodePatch( name = "Spoof video streams", @@ -238,6 +239,17 @@ fun spoofVideoStreamsPatch( // 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() } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index 6cbc47b7d..148c49c33 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -1,6 +1,7 @@ package app.revanced.patches.youtube.misc.debugging import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources 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.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;" @@ -61,19 +64,17 @@ val enableDebuggingPatch = bytecodePatch( experimentalBooleanFeatureFlagFingerprint.match( experimentalFeatureFlagParentFingerprint.originalClassDef ).method.apply { - val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) + findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> + val register = getInstruction(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( - insertIndex, - """ - move-result v0 - invoke-static { v0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZJ)Z - move-result v0 - return v0 - """ - ) + addInstructions( + index, + """ + invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z + move-result v$register + """ + ) + } } experimentalDoubleFeatureFlagFingerprint.match( @@ -92,7 +93,6 @@ val enableDebuggingPatch = bytecodePatch( ) } - experimentalLongFeatureFlagFingerprint.match( experimentalFeatureFlagParentFingerprint.originalClassDef ).method.apply { @@ -108,21 +108,22 @@ val enableDebuggingPatch = bytecodePatch( """ ) - experimentalStringFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) + } - addInstructions( - insertIndex, - """ - move-result-object v0 - invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; - move-result-object v0 - return-object v0 - """ - ) - } + experimentalStringFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) + + addInstructions( + insertIndex, + """ + move-result-object v0 + invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 + """ + ) } // There exists other experimental accessor methods for byte[] diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt index fe3f75eb1..33e188974 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt @@ -11,9 +11,9 @@ internal val experimentalFeatureFlagParentFingerprint = fingerprint { } internal val experimentalBooleanFeatureFlagFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) returns("Z") - parameters("J", "Z") + parameters("L", "J", "Z") } internal val experimentalDoubleFeatureFlagFingerprint = fingerprint { @@ -33,4 +33,3 @@ internal val experimentalStringFeatureFlagFingerprint = fingerprint { returns("Ljava/lang/String;") parameters("J", "Ljava/lang/String;") } - diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt index c538bcecd..28a913c38 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt @@ -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.SwitchPreference 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.settingsPatch @@ -25,7 +27,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({ dependsOn( userAgentClientSpoofPatch, settingsPatch, + versionCheckPatch ) +}, { + is_19_34_or_greater }, { addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch") diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 8e5a85056..58cdd9098 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -408,10 +408,13 @@ internal fun MutableMethod.insertFeatureFlagBooleanOverride(literal: Long, exten val index = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) val register = getInstruction(index).registerA + val operation = if (register < 16) "invoke-static { v$register }" + else "invoke-static/range { v$register .. v$register }" + addInstructions( index + 1, """ - invoke-static { v$register }, $extensionsMethod + $operation, $extensionsMethod move-result v$register """ ) @@ -458,7 +461,7 @@ fun MutableMethod.returnEarly(bool: Boolean = false) { return v0 """ - else -> throw Exception("This case should never happen.") + else -> throw Exception("Return type is not supported: $this") } addInstructions(0, stringInstructions)