diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/SpoofFormatStreamDataPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/SpoofFormatStreamDataPatch.kt new file mode 100644 index 000000000..0924e879d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/SpoofFormatStreamDataPatch.kt @@ -0,0 +1,123 @@ +package app.revanced.patches.youtube.utils.fix.formatstream + +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.patch.PatchException +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.utils.compatibility.Constants +import app.revanced.patches.youtube.utils.fix.formatstream.fingerprints.FormatStreamModelConstructorFingerprint +import app.revanced.patches.youtube.utils.fix.formatstream.fingerprints.VideoStreamingDataConstructorFingerprint +import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.patches.youtube.video.information.VideoInformationPatch +import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch +import app.revanced.patches.youtube.video.videoid.VideoIdPatch +import app.revanced.util.addFieldAndInstructions +import app.revanced.util.getTargetIndex +import app.revanced.util.patch.BaseBytecodePatch +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +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 + +@Suppress("unused") +object SpoofFormatStreamDataPatch : BaseBytecodePatch( + name = "Spoof format stream data", + description = "Adds options to spoof format stream data to prevent playback issues.", + dependencies = setOf( + PlayerResponseMethodHookPatch::class, + SettingsPatch::class, + VideoIdPatch::class, + VideoInformationPatch::class, + ), + compatiblePackages = Constants.COMPATIBLE_PACKAGE, + fingerprints = setOf( + FormatStreamModelConstructorFingerprint, + VideoStreamingDataConstructorFingerprint + ) +) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$MISC_PATH/SpoofFormatStreamDataPatch;" + + override fun execute(context: BytecodeContext) { + + // hook player response video id, to start loading format stream data sooner in the background. + VideoIdPatch.hookPlayerResponseVideoId("$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseVideoId(Ljava/lang/String;Z)V") + + // TODO: Check if all instructions need to be spoofed. + FormatStreamModelConstructorFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val formatStreamDataIndex = it.scanResult.patternScanResult!!.startIndex + val formatStreamDataReference = getInstruction(formatStreamDataIndex).reference + val formatStreamDataClass = context.findClass((formatStreamDataReference as FieldReference).definingClass)!!.mutableClass + + formatStreamDataClass.methods.find { method -> method.name == "" } + ?.apply { + val getSmaliInstructions = + """ + if-eqz v0, :ignore + iget-object v0, v0, $formatStreamDataReference + if-eqz v0, :ignore + return-object v0 + :ignore + const-string v0, "" + return-object v0 + """ + val setSmaliInstructions = + """ + if-eqz p0, :ignore + if-eqz v0, :ignore + iput-object p0, v0, $formatStreamDataReference + :ignore + return-void + """ + + val integrationMutableClass = + context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass + + integrationMutableClass.addFieldAndInstructions( + context, + "getFormatStreamData", + "formatStreamDataClass", + definingClass, + getSmaliInstructions, + true + ) + integrationMutableClass.addFieldAndInstructions( + context, + "setFormatStreamData", + "formatStreamDataClass", + definingClass, + setSmaliInstructions, + true + ) + } ?: throw PatchException("FormatStreamDataClass not found!") + + hook() + } + } + + VideoStreamingDataConstructorFingerprint.resultOrThrow().mutableMethod.hook() + + /** + * Add settings + */ + SettingsPatch.addPreference( + arrayOf( + "PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS", + "SETTINGS: SPOOF_FORMAT_STREAM_DATA" + ) + ) + + SettingsPatch.updatePatchStatus(this) + } + + private fun MutableMethod.hook() = + addInstruction( + 1, + "invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->hookFormatStreamData()V" + ) +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/FormatStreamModelConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/FormatStreamModelConstructorFingerprint.kt new file mode 100644 index 000000000..732028a31 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/FormatStreamModelConstructorFingerprint.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.youtube.utils.fix.formatstream.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.fix.formatstream.fingerprints.FormatStreamModelConstructorFingerprint.indexOfParseInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +internal object FormatStreamModelConstructorFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + returnType = "V", + opcodes = listOf( + Opcode.IGET_OBJECT, // get formatStreamData + Opcode.INVOKE_STATIC // Uri.parse(String formatStreamData) + ), + customFingerprint = { methodDef, classDef -> + classDef.type == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;" && + indexOfParseInstruction(methodDef) >= 0 + } +) { + fun indexOfParseInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_STATIC && getReference()?.name == "parse" + } +} + diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/VideoStreamingDataConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/VideoStreamingDataConstructorFingerprint.kt new file mode 100644 index 000000000..710f25107 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/formatstream/fingerprints/VideoStreamingDataConstructorFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.utils.fix.formatstream.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object VideoStreamingDataConstructorFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + returnType = "V", + customFingerprint = { _, classDef -> + classDef.type == "Lcom/google/android/libraries/youtube/innertube/model/media/VideoStreamingData;" + } +) + diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index 2cb9fa252..4d5d81355 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -1366,6 +1366,13 @@ Tap on the continue button and disable battery optimizations." Removes tracking query parameters from the URLs when sharing links. Disable QUIC protocol "Disable CronetEngine's QUIC protocol." + Spoof format stream data + "Spoofs format stream data to prevent playback issues. + +Limitations: +• There may be about 5 seconds of buffering when the video starts. +• If buffering takes too long, you may need to close and reopen the video. +• Since it is still under development, there may be other unknown issues." Spoof player parameter "Spoofs player parameters to prevent playback issues. diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index 2af4485a1..a89b37bfc 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -551,6 +551,9 @@ + + @@ -626,7 +629,7 @@ - +