feat(YouTube Music): Add Spoof streaming data patch

This commit is contained in:
inotia00
2024-12-15 18:29:05 +09:00
parent 21acf6f003
commit 7dfd817ba3
26 changed files with 664 additions and 649 deletions

View File

@ -46,6 +46,7 @@ private const val CLIENT_INFO_CLASS_DESCRIPTOR =
val spoofClientPatch = bytecodePatch(
SPOOF_CLIENT.title,
SPOOF_CLIENT.summary,
false,
) {
dependsOn(settingsPatch)

View File

@ -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)
}
)

View File

@ -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",

View File

@ -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;"

View File

@ -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()
}
}

View File

@ -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=\""),
)

View File

@ -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
}
}
)

View File

@ -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",

View File

@ -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