mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-13 05:37:40 +02:00
feat(YouTube Music): Add Spoof streaming data
patch
This commit is contained in:
@ -46,6 +46,7 @@ private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
||||
val spoofClientPatch = bytecodePatch(
|
||||
SPOOF_CLIENT.title,
|
||||
SPOOF_CLIENT.summary,
|
||||
false,
|
||||
) {
|
||||
dependsOn(settingsPatch)
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package app.revanced.patches.music.utils.fix.streamingdata
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
|
||||
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
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.reference.FieldReference
|
||||
|
||||
private const val DEFAULT_CLIENT_TYPE = "ANDROID_VR"
|
||||
|
||||
val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
|
||||
{
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME),
|
||||
settingsPatch,
|
||||
)
|
||||
},
|
||||
{
|
||||
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
|
||||
name == "SpoofStreamingDataDefaultClient"
|
||||
}.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(0).registerA
|
||||
val type = (getInstruction<ReferenceInstruction>(0).reference as FieldReference).type
|
||||
replaceInstruction(
|
||||
0,
|
||||
"sget-object v$register, $type->$DEFAULT_CLIENT_TYPE:$type"
|
||||
)
|
||||
}
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_streaming_data",
|
||||
"true"
|
||||
)
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_streaming_data_stats_for_nerds",
|
||||
"true",
|
||||
"revanced_spoof_streaming_data"
|
||||
)
|
||||
|
||||
updatePatchStatus(SPOOF_STREAMING_DATA)
|
||||
|
||||
}
|
||||
)
|
@ -143,7 +143,11 @@ internal enum class PatchList(
|
||||
),
|
||||
SPOOF_CLIENT(
|
||||
"Spoof client",
|
||||
"Adds options to spoof the client to allow track playback."
|
||||
"Adds options to spoof the client to allow playback."
|
||||
),
|
||||
SPOOF_STREAMING_DATA(
|
||||
"Spoof streaming data",
|
||||
"Adds options to spoof the streaming data to allow playback."
|
||||
),
|
||||
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
|
||||
"Translations for YouTube Music",
|
||||
|
@ -6,6 +6,7 @@ internal object Constants {
|
||||
const val PATCHES_PATH = "$EXTENSION_PATH/patches"
|
||||
const val COMPONENTS_PATH = "$PATCHES_PATH/components"
|
||||
const val SPANS_PATH = "$PATCHES_PATH/spans"
|
||||
const val SPOOF_PATH = "$PATCHES_PATH/spoof"
|
||||
|
||||
const val EXTENSION_UTILS_PATH = "$EXTENSION_PATH/utils"
|
||||
const val EXTENSION_SETTING_CLASS_DESCRIPTOR = "$EXTENSION_PATH/settings/Setting;"
|
||||
|
@ -0,0 +1,215 @@
|
||||
package app.revanced.patches.shared.spoof.streamingdata
|
||||
|
||||
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.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
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
|
||||
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
fun baseSpoofStreamingDataPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "Spoof streaming data",
|
||||
description = "Adds options to spoof the streaming data to allow playback."
|
||||
) {
|
||||
block()
|
||||
|
||||
execute {
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
buildInitPlaybackRequestFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val moveUriStringIndex = it.patternMatch!!.startIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $EXTENSION_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.methodOrThrow().apply {
|
||||
val invokeToStringIndex = indexOfToStringInstruction(this)
|
||||
val uriRegister =
|
||||
getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
buildRequestFingerprint.methodOrThrow().apply {
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
val urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val mapRegister = if (entrySetIndex < 0)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
var smaliInstructions =
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"fetchStreams(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.matchOrThrow(createStreamingDataParentFingerprint).let { result ->
|
||||
result.method.apply {
|
||||
val setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||
val setStreamingDataField =
|
||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>().toString()
|
||||
|
||||
val playerProtoClass =
|
||||
getInstruction(setStreamingDataIndex + 1).getReference<FieldReference>()!!.definingClass
|
||||
val protobufClass =
|
||||
protobufClassParseByteBufferFingerprint.definingClassOrThrow()
|
||||
|
||||
val getStreamingDataField = instructions.find { instruction ->
|
||||
instruction.opcode == Opcode.IGET_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find getStreamingDataField")
|
||||
|
||||
val videoDetailsIndex = result.patternMatch!!.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 { }, $EXTENSION_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 }, $EXTENSION_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 v$freeRegister, :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.methodOrThrow().apply {
|
||||
val targetIndex = instructions.lastIndex
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
# Field a: Stream uri.
|
||||
# Field c: Http method.
|
||||
# Field d: Post data.
|
||||
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 }, $EXTENSION_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.methodOrThrow().apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index, """
|
||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
executeBlock()
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.utils.fix.streamingdata
|
||||
package app.revanced.patches.shared.spoof.streamingdata
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
@ -10,38 +10,6 @@ import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val buildBrowseRequestFingerprint = legacyFingerprint(
|
||||
name = "buildBrowseRequestFingerprint",
|
||||
customFingerprint = { method, _ ->
|
||||
method.implementation != null &&
|
||||
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
|
||||
!method.definingClass.startsWith("Lorg/") &&
|
||||
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
|
||||
// YouTube 17.34.36 ~ YouTube 18.35.36
|
||||
(indexOfEntrySetInstruction(method) >= 0 ||
|
||||
// YouTube 18.36.39 ~
|
||||
method.parameters[1].type == "Ljava/util/Map;")
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setRequestFinishedListener"
|
||||
}
|
||||
|
||||
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
|
||||
method.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;"
|
||||
}
|
||||
|
||||
internal fun indexOfEntrySetInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
|
||||
}
|
||||
|
||||
internal val buildInitPlaybackRequestFingerprint = legacyFingerprint(
|
||||
name = "buildInitPlaybackRequestFingerprint",
|
||||
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
|
||||
@ -91,6 +59,38 @@ internal fun indexOfToStringInstruction(method: Method) =
|
||||
getReference<MethodReference>().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;"
|
||||
}
|
||||
|
||||
internal val buildRequestFingerprint = legacyFingerprint(
|
||||
name = "buildRequestFingerprint",
|
||||
customFingerprint = { method, _ ->
|
||||
method.implementation != null &&
|
||||
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
|
||||
!method.definingClass.startsWith("Lorg/") &&
|
||||
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
|
||||
// Earlier targets
|
||||
(indexOfEntrySetInstruction(method) >= 0 ||
|
||||
// Later targets
|
||||
method.parameters[1].type == "Ljava/util/Map;")
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setRequestFinishedListener"
|
||||
}
|
||||
|
||||
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
|
||||
method.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;"
|
||||
}
|
||||
|
||||
internal fun indexOfEntrySetInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
|
||||
}
|
||||
|
||||
internal val createStreamingDataFingerprint = legacyFingerprint(
|
||||
name = "createStreamingDataFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
@ -103,19 +103,21 @@ internal val createStreamingDataFingerprint = legacyFingerprint(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IPUT_OBJECT
|
||||
),
|
||||
customFingerprint = { method, _ ->
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.SGET_OBJECT &&
|
||||
getReference<FieldReference>()?.name == "playerThreedRenderer"
|
||||
} >= 0
|
||||
},
|
||||
)
|
||||
|
||||
internal val createStreamingDataParentFingerprint = legacyFingerprint(
|
||||
name = "createStreamingDataParentFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "L",
|
||||
parameters = emptyList(),
|
||||
strings = listOf("Invalid playback type; streaming data is not playable"),
|
||||
)
|
||||
|
||||
internal val nerdsStatsVideoFormatBuilderFingerprint = legacyFingerprint(
|
||||
name = "nerdsStatsVideoFormatBuilderFingerprint",
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"),
|
||||
parameters = listOf("L"),
|
||||
strings = listOf("codecs=\""),
|
||||
)
|
||||
|
@ -1,222 +1,23 @@
|
||||
package app.revanced.patches.youtube.utils.fix.streamingdata
|
||||
|
||||
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.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
|
||||
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
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
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
val spoofStreamingDataPatch = bytecodePatch(
|
||||
SPOOF_STREAMING_DATA.title,
|
||||
SPOOF_STREAMING_DATA.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
baseSpoofUserAgentPatch("com.google.android.youtube"),
|
||||
settingsPatch
|
||||
)
|
||||
|
||||
execute {
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
buildPlayerRequestURIFingerprint.methodOrThrow().apply {
|
||||
val invokeToStringIndex = indexOfToStringInstruction(this)
|
||||
val uriRegister =
|
||||
getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
buildInitPlaybackRequestFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val moveUriStringIndex = it.patternMatch!!.startIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fetch replacement streams.
|
||||
|
||||
buildBrowseRequestFingerprint.methodOrThrow().apply {
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
val urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val mapRegister = if (entrySetIndex < 0)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
var smaliInstructions =
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"fetchStreams(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.matchOrThrow().let { result ->
|
||||
result.method.apply {
|
||||
val setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||
val setStreamingDataField =
|
||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>().toString()
|
||||
|
||||
val playerProtoClass =
|
||||
getInstruction(setStreamingDataIndex + 1).getReference<FieldReference>()!!.definingClass
|
||||
val protobufClass =
|
||||
protobufClassParseByteBufferFingerprint.definingClassOrThrow()
|
||||
|
||||
val getStreamingDataField = instructions.find { instruction ->
|
||||
instruction.opcode == Opcode.IGET_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find getStreamingDataField")
|
||||
|
||||
val videoDetailsIndex = result.patternMatch!!.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 { }, $EXTENSION_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 }, $EXTENSION_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 v$freeRegister, :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.methodOrThrow().apply {
|
||||
val targetIndex = instructions.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 }, $EXTENSION_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.methodOrThrow().apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index, """
|
||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region add settings
|
||||
val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
|
||||
{
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
|
||||
settingsPatch
|
||||
)
|
||||
},
|
||||
{
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"SETTINGS: SPOOF_STREAMING_DATA"
|
||||
@ -224,7 +25,5 @@ val spoofStreamingDataPatch = bytecodePatch(
|
||||
SPOOF_STREAMING_DATA
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -223,7 +223,7 @@ internal enum class PatchList(
|
||||
),
|
||||
SPOOF_STREAMING_DATA(
|
||||
"Spoof streaming data",
|
||||
"Adds options to spoof the streaming data to allow video playback."
|
||||
"Adds options to spoof the streaming data to allow playback."
|
||||
),
|
||||
SWIPE_CONTROLS(
|
||||
"Swipe controls",
|
||||
|
@ -55,6 +55,10 @@ internal fun Pair<String, Fingerprint>.matchOrNull(parentFingerprint: Pair<Strin
|
||||
second.matchOrNull(parentClassDef)
|
||||
}
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.methodOrNull(): MutableMethod? =
|
||||
matchOrNull()?.method
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.methodOrThrow(): MutableMethod =
|
||||
second.methodOrNull ?: throw first.exception
|
||||
@ -63,6 +67,14 @@ context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.methodOrThrow(parentFingerprint: Pair<String, Fingerprint>): MutableMethod =
|
||||
matchOrThrow(parentFingerprint).method
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.originalMethodOrThrow(): Method =
|
||||
second.originalMethodOrNull ?: throw first.exception
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.originalMethodOrThrow(parentFingerprint: Pair<String, Fingerprint>): Method =
|
||||
matchOrThrow(parentFingerprint).originalMethod
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal fun Pair<String, Fingerprint>.mutableClassOrThrow(): MutableClass =
|
||||
second.classDefOrNull ?: throw first.exception
|
||||
|
@ -450,7 +450,16 @@ Tap on the continue button and disable battery optimizations."</string>
|
||||
Limitations:
|
||||
• OPUS audio codec may not be supported.
|
||||
• Seekbar thumbnail may not be present.
|
||||
• Watch history does not work with a brand account.</string>
|
||||
• Watch history does not work with a brand account."</string>
|
||||
|
||||
<string name="revanced_spoof_streaming_data_title">Spoof streaming data</string>
|
||||
<string name="revanced_spoof_streaming_data_summary">Spoof the streaming data to prevent playback issues.</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Show in Stats for nerds</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Shows the client used to fetch streaming data in Stats for nerds.</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_ios">iOS</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_ios_music">iOS Music</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_unplugged">Android TV</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_vr">Android VR</string>
|
||||
|
||||
<string name="revanced_sanitize_sharing_links_title">Sanitize sharing links</string>
|
||||
<string name="revanced_sanitize_sharing_links_summary">Removes tracking query parameters from URLs when sharing links.</string>
|
||||
|
@ -1895,6 +1895,7 @@ Tap on the continue button and disable battery optimizations."</string>
|
||||
<string name="revanced_spoof_streaming_data_user_dialog_message">Turning off this setting may cause video playback issues.</string>
|
||||
<string name="revanced_spoof_streaming_data_type_title">Default client</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_ios">iOS</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_ios_music">iOS Music</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_unplugged">Android TV</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_vr">Android VR</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_title">Spoofing side effects</string>
|
||||
|
@ -778,10 +778,10 @@
|
||||
<!-- SETTINGS: SPOOF_STREAMING_DATA
|
||||
<PreferenceScreen android:title="@string/revanced_preference_screen_spoof_streaming_data_title" android:key="revanced_preference_screen_spoof_streaming_data" android:summary="@string/revanced_preference_screen_spoof_streaming_data_summary">
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_title" android:key="revanced_spoof_streaming_data" android:summaryOn="@string/revanced_spoof_streaming_data_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_summary_off" />
|
||||
<ListPreference android:entries="@array/revanced_spoof_streaming_data_type_entries" android:title="@string/revanced_spoof_streaming_data_type_title" android:key="revanced_spoof_streaming_data_type" android:entryValues="@array/revanced_spoof_streaming_data_type_entry_values" />
|
||||
<ListPreference android:entries="@array/revanced_spoof_streaming_data_type_entries" android:title="@string/revanced_spoof_streaming_data_type_title" android:key="revanced_spoof_streaming_data_type" android:entryValues="@array/revanced_spoof_streaming_data_type_entry_values" android:dependency="revanced_spoof_streaming_data" />
|
||||
<app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference android:title="@string/revanced_spoof_streaming_data_side_effects_title" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_title" android:key="revanced_spoof_streaming_data_ios_skip_livestream_playback" android:summaryOn="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_stats_for_nerds_title" android:key="revanced_spoof_streaming_data_stats_for_nerds" android:summaryOn="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_title" android:key="revanced_spoof_streaming_data_ios_skip_livestream_playback" android:summaryOn="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_ios_skip_livestream_playback_summary_off" android:dependency="revanced_spoof_streaming_data" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_stats_for_nerds_title" android:key="revanced_spoof_streaming_data_stats_for_nerds" android:summaryOn="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_off" android:dependency="revanced_spoof_streaming_data" />
|
||||
</PreferenceScreen>SETTINGS: SPOOF_STREAMING_DATA -->
|
||||
|
||||
<!-- SETTINGS: WATCH_HISTORY
|
||||
|
Reference in New Issue
Block a user