forked from inotia00/revanced-patches
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(YouTube): add
Spoof streaming data
patch
- Loading branch information
Showing
15 changed files
with
513 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
261 changes: 261 additions & 0 deletions
261
...in/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata | ||
|
||
import app.revanced.patcher.data.BytecodeContext | ||
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.getInstructions | ||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction | ||
import app.revanced.patcher.patch.PatchException | ||
import app.revanced.patcher.util.smali.ExternalLabel | ||
import app.revanced.patches.youtube.utils.compatibility.Constants | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildInitPlaybackRequestFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildMediaDataSourceFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildPlayerRequestURIFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildRequestFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.CreateStreamingDataFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.NerdsStatsVideoFormatBuilderFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.ProtobufClassParseByteBufferFingerprint | ||
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH | ||
import app.revanced.patches.youtube.utils.settings.SettingsPatch | ||
import app.revanced.util.getReference | ||
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.FiveRegisterInstruction | ||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction | ||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction | ||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference | ||
|
||
object SpoofStreamingDataPatch : BaseBytecodePatch( | ||
name = "Spoof streaming data", | ||
description = "Adds options to spoof the streaming data to allow video playback.", | ||
dependencies = setOf( | ||
SettingsPatch::class, | ||
SpoofUserAgentPatch::class, | ||
), | ||
compatiblePackages = Constants.COMPATIBLE_PACKAGE, | ||
fingerprints = setOf( | ||
BuildInitPlaybackRequestFingerprint, | ||
BuildMediaDataSourceFingerprint, | ||
BuildPlayerRequestURIFingerprint, | ||
BuildRequestFingerprint, | ||
CreateStreamingDataFingerprint, | ||
ProtobufClassParseByteBufferFingerprint, | ||
|
||
// Nerds stats video format. | ||
NerdsStatsVideoFormatBuilderFingerprint, | ||
) | ||
) { | ||
private const val INTEGRATIONS_CLASS_DESCRIPTOR = | ||
"$MISC_PATH/SpoofStreamingDataPatch;" | ||
private const val REQUEST_CLASS_DESCRIPTOR = | ||
"Lorg/chromium/net/UrlRequest;" | ||
private const val REQUEST_BUILDER_CLASS_DESCRIPTOR = | ||
"Lorg/chromium/net/UrlRequest\$Builder;" | ||
|
||
override fun execute(context: BytecodeContext) { | ||
|
||
// region Block /initplayback requests to fall back to /get_watch requests. | ||
|
||
BuildInitPlaybackRequestFingerprint.resultOrThrow().let { | ||
it.mutableMethod.apply { | ||
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex | ||
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA | ||
|
||
addInstructions( | ||
moveUriStringIndex + 1, | ||
""" | ||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String; | ||
move-result-object v$targetRegister | ||
""", | ||
) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
// region Block /get_watch requests to fall back to /player requests. | ||
|
||
BuildPlayerRequestURIFingerprint.resultOrThrow().let { | ||
it.mutableMethod.apply { | ||
val invokeToStringIndex = BuildPlayerRequestURIFingerprint.indexOfToStringInstruction(this) | ||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC | ||
|
||
addInstructions( | ||
invokeToStringIndex, | ||
""" | ||
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; | ||
move-result-object v$uriRegister | ||
""", | ||
) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
// region Fetch replacement streams. | ||
|
||
BuildRequestFingerprint.resultOrThrow().let { result -> | ||
result.mutableMethod.apply { | ||
val buildRequestIndex = | ||
BuildRequestFingerprint.indexOfBuildUrlRequestInstruction(this) | ||
val requestBuilderRegister = getInstruction<FiveRegisterInstruction>(buildRequestIndex).registerC | ||
|
||
val newRequestBuilderIndex = | ||
BuildRequestFingerprint.indexOfNewUrlRequestBuilderInstruction(this) | ||
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD | ||
|
||
// Replace "requestBuilder.build()" with integrations call. | ||
replaceInstruction( | ||
buildRequestIndex, | ||
"invoke-static { v$requestBuilderRegister }, " + | ||
"$INTEGRATIONS_CLASS_DESCRIPTOR->" + | ||
"buildRequest($REQUEST_BUILDER_CLASS_DESCRIPTOR)" + | ||
REQUEST_CLASS_DESCRIPTOR | ||
) | ||
|
||
val entrySetIndex = BuildRequestFingerprint.indexOfEntrySetInstruction(this) | ||
val mapRegister = if (entrySetIndex < 0) | ||
urlRegister + 1 | ||
else | ||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC | ||
|
||
var smaliInstructions = | ||
"invoke-static { v$urlRegister, v$mapRegister }, " + | ||
"$INTEGRATIONS_CLASS_DESCRIPTOR->" + | ||
"setHeader(Ljava/lang/String;Ljava/util/Map;)V" | ||
|
||
if (entrySetIndex < 0) smaliInstructions = """ | ||
move-object/from16 v$mapRegister, p1 | ||
""" + smaliInstructions | ||
|
||
// Copy request headers for streaming data fetch. | ||
addInstructions(newRequestBuilderIndex + 2, smaliInstructions) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
// region Replace the streaming data. | ||
|
||
CreateStreamingDataFingerprint.resultOrThrow().let { result -> | ||
result.mutableMethod.apply { | ||
val setStreamingDataIndex = result.scanResult.patternScanResult!!.startIndex | ||
val setStreamingDataField = getInstruction(setStreamingDataIndex).getReference<FieldReference>().toString() | ||
|
||
val playerProtoClass = getInstruction(setStreamingDataIndex + 1).getReference<FieldReference>()!!.definingClass | ||
val protobufClass = ProtobufClassParseByteBufferFingerprint.resultOrThrow().mutableMethod.definingClass | ||
|
||
val getStreamingDataField = getInstructions().find { instruction -> | ||
instruction.opcode == Opcode.IGET_OBJECT && | ||
instruction.getReference<FieldReference>()?.definingClass == playerProtoClass | ||
}?.getReference<FieldReference>() | ||
?: throw PatchException("Could not find getStreamingDataField") | ||
|
||
val videoDetailsIndex = result.scanResult.patternScanResult!!.endIndex | ||
val videoDetailsClass = getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type | ||
|
||
val insertIndex = videoDetailsIndex + 1 | ||
val videoDetailsRegister = getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA | ||
|
||
val overrideRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA | ||
val freeRegister = implementation!!.registerCount - parameters.size - 2 | ||
|
||
addInstructionsWithLabels( | ||
insertIndex, | ||
""" | ||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isSpoofingEnabled()Z | ||
move-result v$freeRegister | ||
if-eqz v$freeRegister, :disabled | ||
# Get video id. | ||
# From YouTube 17.34.36 to YouTube 19.16.39, the field names and field types are the same. | ||
iget-object v$freeRegister, v$videoDetailsRegister, $videoDetailsClass->c:Ljava/lang/String; | ||
if-eqz v$freeRegister, :disabled | ||
# Get streaming data. | ||
invoke-static { v$freeRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer; | ||
move-result-object v$freeRegister | ||
if-eqz v$freeRegister, :disabled | ||
# Parse streaming data. | ||
sget-object v$overrideRegister, $playerProtoClass->a:$playerProtoClass | ||
invoke-static { v$overrideRegister, v$freeRegister }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass | ||
move-result-object v$freeRegister | ||
check-cast v$freeRegister, $playerProtoClass | ||
# Set streaming data. | ||
iget-object v$freeRegister, v$freeRegister, $getStreamingDataField | ||
if-eqz v0, :disabled | ||
iput-object v$freeRegister, p0, $setStreamingDataField | ||
""", | ||
ExternalLabel("disabled", getInstruction(insertIndex)) | ||
) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
// region Remove /videoplayback request body to fix playback. | ||
// This is needed when using iOS client as streaming data source. | ||
|
||
BuildMediaDataSourceFingerprint.resultOrThrow().let { | ||
it.mutableMethod.apply { | ||
val targetIndex = getInstructions().lastIndex | ||
|
||
addInstructions( | ||
targetIndex, | ||
""" | ||
# Field a: Stream uri. | ||
# Field c: Http method. | ||
# Field d: Post data. | ||
# From YouTube 17.34.36 to YouTube 19.16.39, the field names and field types are the same. | ||
move-object/from16 v0, p0 | ||
iget-object v1, v0, $definingClass->a:Landroid/net/Uri; | ||
iget v2, v0, $definingClass->c:I | ||
iget-object v3, v0, $definingClass->d:[B | ||
invoke-static { v1, v2, v3 }, $INTEGRATIONS_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B | ||
move-result-object v1 | ||
iput-object v1, v0, $definingClass->d:[B | ||
""", | ||
) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
// region Append spoof info. | ||
|
||
NerdsStatsVideoFormatBuilderFingerprint.resultOrThrow().mutableMethod.apply { | ||
for (index in implementation!!.instructions.size - 1 downTo 0) { | ||
val instruction = getInstruction(index) | ||
if (instruction.opcode != Opcode.RETURN_OBJECT) | ||
continue | ||
|
||
val register = (instruction as OneRegisterInstruction).registerA | ||
|
||
addInstructions( | ||
index, """ | ||
invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; | ||
move-result-object v$register | ||
""" | ||
) | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
/** | ||
* Add settings | ||
*/ | ||
SettingsPatch.addPreference( | ||
arrayOf( | ||
"SETTINGS: SPOOF_STREAMING_DATA" | ||
) | ||
) | ||
|
||
SettingsPatch.updatePatchStatus(this) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofUserAgentPatch.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata | ||
|
||
import app.revanced.patches.shared.spoofuseragent.BaseSpoofUserAgentPatch | ||
|
||
object SpoofUserAgentPatch : BaseSpoofUserAgentPatch("com.google.android.youtube") |
16 changes: 16 additions & 0 deletions
16
...tches/youtube/utils/fix/streamingdata/fingerprints/BuildInitPlaybackRequestFingerprint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.Opcode | ||
|
||
internal object BuildInitPlaybackRequestFingerprint : MethodFingerprint( | ||
returnType = "Lorg/chromium/net/UrlRequest\$Builder;", | ||
opcodes = listOf( | ||
Opcode.MOVE_RESULT_OBJECT, | ||
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with. | ||
), | ||
strings = listOf( | ||
"Content-Type", | ||
"Range", | ||
), | ||
) |
22 changes: 22 additions & 0 deletions
22
...d/patches/youtube/utils/fix/streamingdata/fingerprints/BuildMediaDataSourceFingerprint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object BuildMediaDataSourceFingerprint : MethodFingerprint( | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, | ||
returnType = "V", | ||
parameters = listOf( | ||
"Landroid/net/Uri;", | ||
"J", | ||
"I", | ||
"[B", | ||
"Ljava/util/Map;", | ||
"J", | ||
"J", | ||
"Ljava/lang/String;", | ||
"I", | ||
"Ljava/lang/Object;" | ||
) | ||
) |
26 changes: 26 additions & 0 deletions
26
.../patches/youtube/utils/fix/streamingdata/fingerprints/BuildPlayerRequestURIFingerprint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildPlayerRequestURIFingerprint.indexOfToStringInstruction | ||
import app.revanced.util.getReference | ||
import app.revanced.util.indexOfFirstInstruction | ||
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 BuildPlayerRequestURIFingerprint : MethodFingerprint( | ||
returnType = "Ljava/lang/String;", | ||
strings = listOf( | ||
"key", | ||
"asig", | ||
), | ||
customFingerprint = { methodDef, _ -> | ||
indexOfToStringInstruction(methodDef) >= 0 | ||
}, | ||
) { | ||
fun indexOfToStringInstruction(methodDef: Method) = | ||
methodDef.indexOfFirstInstruction { | ||
opcode == Opcode.INVOKE_VIRTUAL && | ||
getReference<MethodReference>().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
.../revanced/patches/youtube/utils/fix/streamingdata/fingerprints/BuildRequestFingerprint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildRequestFingerprint.indexOfBuildUrlRequestInstruction | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildRequestFingerprint.indexOfEntrySetInstruction | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildRequestFingerprint.indexOfNewUrlRequestBuilderInstruction | ||
import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildRequestFingerprint.indexOfRequestFinishedListenerInstruction | ||
import app.revanced.util.getReference | ||
import app.revanced.util.indexOfFirstInstruction | ||
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 BuildRequestFingerprint : MethodFingerprint( | ||
customFingerprint = { methodDef, _ -> | ||
methodDef.implementation != null && | ||
indexOfRequestFinishedListenerInstruction(methodDef) >= 0 && | ||
!methodDef.definingClass.startsWith("Lorg/") && | ||
indexOfNewUrlRequestBuilderInstruction(methodDef) >= 0 && | ||
indexOfBuildUrlRequestInstruction(methodDef) >= 0 && | ||
// YouTube 17.34.36 ~ YouTube 18.35.36 | ||
(indexOfEntrySetInstruction(methodDef) >= 0 || | ||
// YouTube 18.36.39 ~ | ||
methodDef.parameters[1].type == "Ljava/util/Map;") | ||
} | ||
) { | ||
fun indexOfRequestFinishedListenerInstruction(methodDef: Method) = | ||
methodDef.indexOfFirstInstruction { | ||
opcode == Opcode.INVOKE_VIRTUAL && | ||
getReference<MethodReference>().toString() == "Lorg/chromium/net/ExperimentalUrlRequest${'$'}Builder;->setRequestFinishedListener(Lorg/chromium/net/RequestFinishedInfo${'$'}Listener;)Lorg/chromium/net/ExperimentalUrlRequest${'$'}Builder;" | ||
} | ||
|
||
fun indexOfNewUrlRequestBuilderInstruction(methodDef: Method) = | ||
methodDef.indexOfFirstInstruction { | ||
opcode == Opcode.INVOKE_VIRTUAL && | ||
getReference<MethodReference>().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;" | ||
} | ||
|
||
fun indexOfBuildUrlRequestInstruction(methodDef: Method) = | ||
methodDef.indexOfFirstInstruction { | ||
opcode == Opcode.INVOKE_VIRTUAL && | ||
getReference<MethodReference>().toString() == "Lorg/chromium/net/ExperimentalUrlRequest${'$'}Builder;->build()Lorg/chromium/net/ExperimentalUrlRequest;" | ||
} | ||
|
||
fun indexOfEntrySetInstruction(methodDef: Method) = | ||
methodDef.indexOfFirstInstruction { | ||
opcode == Opcode.INVOKE_INTERFACE && | ||
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;" | ||
} | ||
} |
Oops, something went wrong.