mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-13 13:47:42 +02:00
fix(YouTube/Spoof client): restore playback speed menu when spoofing to an iOS, Android TV, Android Testsuite client 95f290f113
This commit is contained in:
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.youtube.utils.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.VarispeedUnavailableTitle
|
||||
import app.revanced.util.fingerprint.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object PlaybackRateBottomSheetBuilderFingerprint : LiteralValueFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.IGET_BOOLEAN,
|
||||
Opcode.IF_EQZ,
|
||||
),
|
||||
literalSupplier = { VarispeedUnavailableTitle }
|
||||
)
|
@ -11,20 +11,23 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMu
|
||||
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
|
||||
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants
|
||||
import app.revanced.patches.youtube.utils.fingerprints.PlaybackRateBottomSheetBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildInitPlaybackRequestFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildPlayerRequestURIFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlaybackSpeedMenuItemFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.NerdsStatsVideoFormatBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.PlayerGestureConfigSyntheticFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.SetPlayerRequestClientTypeFingerprint
|
||||
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
|
||||
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.util.getReference
|
||||
import app.revanced.util.getStringInstructionIndex
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.patch.BaseBytecodePatch
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@ -42,11 +45,11 @@ object SpoofClientPatch : BaseBytecodePatch(
|
||||
name = "Spoof client",
|
||||
description = "Adds options to spoof the client to allow video playback.",
|
||||
dependencies = setOf(
|
||||
PlayerTypeHookPatch::class,
|
||||
PlayerResponseMethodHookPatch::class,
|
||||
SettingsPatch::class,
|
||||
VideoInformationPatch::class,
|
||||
SpoofUserAgentPatch::class,
|
||||
SharedResourceIdPatch::class,
|
||||
),
|
||||
compatiblePackages = Constants.COMPATIBLE_PACKAGE,
|
||||
fingerprints = setOf(
|
||||
@ -60,6 +63,10 @@ object SpoofClientPatch : BaseBytecodePatch(
|
||||
// Player gesture config.
|
||||
PlayerGestureConfigSyntheticFingerprint,
|
||||
|
||||
// Player speed menu item.
|
||||
CreatePlaybackSpeedMenuItemFingerprint,
|
||||
PlaybackRateBottomSheetBuilderFingerprint,
|
||||
|
||||
// Nerds stats video format.
|
||||
NerdsStatsVideoFormatBuilderFingerprint,
|
||||
)
|
||||
@ -257,26 +264,74 @@ object SpoofClientPatch : BaseBytecodePatch(
|
||||
// region fix player gesture.
|
||||
|
||||
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
||||
arrayOf(3, 9).forEach { offSet ->
|
||||
it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex - offSet)
|
||||
.apply {
|
||||
val index = implementation!!.instructions.size - 1
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val downAndOutLandscapeAllowedIndex = endIndex - 3
|
||||
val downAndOutPortraitAllowedIndex = endIndex - 9
|
||||
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
|
||||
move-result v$register
|
||||
arrayOf(
|
||||
downAndOutLandscapeAllowedIndex,
|
||||
downAndOutPortraitAllowedIndex,
|
||||
).forEach { index ->
|
||||
val gestureAllowedMethod = it.getWalkerMethod(context, index)
|
||||
|
||||
gestureAllowedMethod.apply {
|
||||
val isAllowedIndex = getInstructions().lastIndex
|
||||
val isAllowed = getInstruction<OneRegisterInstruction>(isAllowedIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
isAllowedIndex,
|
||||
"""
|
||||
)
|
||||
}
|
||||
invoke-static { v$isAllowed }, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
|
||||
move-result v$isAllowed
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region append spoof info
|
||||
// region fix playback speed menu item.
|
||||
|
||||
// fix for iOS, Android Testsuite
|
||||
CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let {
|
||||
val scanResult = it.scanResult.patternScanResult!!
|
||||
if (scanResult.startIndex != 0) throw PatchException("Unexpected start index: ${scanResult.startIndex}")
|
||||
|
||||
it.mutableMethod.apply {
|
||||
// Find the conditional check if the playback speed menu item is not created.
|
||||
val shouldCreateMenuIndex = indexOfFirstInstructionOrThrow(scanResult.endIndex) { opcode == Opcode.IF_EQZ }
|
||||
val shouldCreateMenuRegister = getInstruction<OneRegisterInstruction>(shouldCreateMenuIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
shouldCreateMenuIndex,
|
||||
"""
|
||||
invoke-static { v$shouldCreateMenuRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenu(Z)Z
|
||||
move-result v$shouldCreateMenuRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// fix for Android TV
|
||||
PlaybackRateBottomSheetBuilderFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val targetIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenuReversed(Z)Z
|
||||
move-result v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region append spoof info.
|
||||
|
||||
NerdsStatsVideoFormatBuilderFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
for (index in implementation!!.instructions.size - 1 downTo 0) {
|
||||
|
@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
opcodes = listOf(
|
||||
Opcode.IGET_OBJECT, // First instruction of the method
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
null // MOVE_RESULT or MOVE_RESULT_OBJECT, Return value controls the creation of the playback speed menu item.
|
||||
),
|
||||
// 19.01 and earlier is missing the second parameter.
|
||||
// Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures.
|
||||
customFingerprint = custom@{ methodDef, _ ->
|
||||
// 19.01 and earlier parameters are: "[L"
|
||||
// 19.02+ parameters are "[L", "F"
|
||||
val parameterTypes = methodDef.parameterTypes
|
||||
val firstParameter = parameterTypes.firstOrNull()
|
||||
|
||||
if (firstParameter == null || !firstParameter.startsWith("[L")) {
|
||||
return@custom false
|
||||
}
|
||||
|
||||
parameterTypes.size == 1 || (parameterTypes.size == 2 && parameterTypes[1] == "F")
|
||||
},
|
||||
)
|
@ -1,9 +1,8 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.PlayerGestureConfigSyntheticFingerprint.indexOfDownAndOutAllowedInstruction
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@ -12,8 +11,7 @@ import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
/**
|
||||
* Annotation [FuzzyPatternScanMethod] is required to maintain compatibility with YouTube v18.29.38 ~ v18.32.39.
|
||||
*
|
||||
* [FuzzyPatternScanMethod] was added to maintain compatibility for YouTube v18.29.38 to v18.32.39.
|
||||
* TODO: Remove this annotation if support for YouTube v18.29.38 to v18.32.39 is dropped.
|
||||
*/
|
||||
@FuzzyPatternScanMethod(5)
|
||||
@ -30,28 +28,28 @@ internal object PlayerGestureConfigSyntheticFingerprint : MethodFingerprint(
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed.
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed.
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.RETURN_VOID,
|
||||
),
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
// This method is always called "a" because this kind of class always has a single method.
|
||||
methodDef.name == "a" && classDef.methods.count() == 2 &&
|
||||
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
|
||||
}
|
||||
) {
|
||||
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
||||
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType == "Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method is always called "a" because this kind of class always has a single method.
|
||||
methodDef.name == "a" && classDef.methods.count() == 2 &&
|
||||
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
|
||||
},
|
||||
)
|
@ -2,9 +2,8 @@ package app.revanced.patches.youtube.utils.flyoutmenu
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.youtube.utils.flyoutmenu.fingerprints.PlaybackRateBottomSheetClassFingerprint
|
||||
import app.revanced.patches.youtube.utils.fingerprints.PlaybackRateBottomSheetBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.flyoutmenu.fingerprints.VideoQualityBottomSheetClassFingerprint
|
||||
import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH
|
||||
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
|
||||
@ -17,7 +16,7 @@ import app.revanced.util.resultOrThrow
|
||||
)
|
||||
object FlyoutMenuHookPatch : BytecodePatch(
|
||||
setOf(
|
||||
PlaybackRateBottomSheetClassFingerprint,
|
||||
PlaybackRateBottomSheetBuilderFingerprint,
|
||||
VideoQualityBottomSheetClassFingerprint
|
||||
)
|
||||
) {
|
||||
@ -30,17 +29,12 @@ object FlyoutMenuHookPatch : BytecodePatch(
|
||||
INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR
|
||||
)!!.mutableClass
|
||||
|
||||
PlaybackRateBottomSheetClassFingerprint.resultOrThrow().let {
|
||||
PlaybackRateBottomSheetBuilderFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val playbackRateBottomSheetBuilderMethodName =
|
||||
it.mutableClass.methods.find { method -> method.parameters.isEmpty() && method.returnType == "V" }
|
||||
?.name
|
||||
?: throw PatchException("Could not find PlaybackRateBottomSheetBuilderMethod")
|
||||
|
||||
val smaliInstructions =
|
||||
"""
|
||||
if-eqz v0, :ignore
|
||||
invoke-virtual {v0}, $definingClass->$playbackRateBottomSheetBuilderMethodName()V
|
||||
invoke-virtual {v0}, $definingClass->$name()V
|
||||
:ignore
|
||||
return-void
|
||||
"""
|
||||
|
@ -1,12 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.flyoutmenu.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object PlaybackRateBottomSheetClassFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("[L", "I"),
|
||||
strings = listOf("menu_item_playback_speed")
|
||||
)
|
@ -95,6 +95,7 @@ object SharedResourceIdPatch : ResourcePatch() {
|
||||
var TotalTime = -1L
|
||||
var TouchArea = -1L
|
||||
var VideoQualityBottomSheet = -1L
|
||||
var VarispeedUnavailableTitle = -1L
|
||||
var VideoQualityUnavailableAnnouncement = -1L
|
||||
var VoiceSearch = -1L
|
||||
var YouTubeControlsOverlaySubtitleButton = -1L
|
||||
@ -187,6 +188,7 @@ object SharedResourceIdPatch : ResourcePatch() {
|
||||
TotalTime = getId(STRING, "total_time")
|
||||
TouchArea = getId(ID, "touch_area")
|
||||
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
|
||||
VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title")
|
||||
VideoQualityUnavailableAnnouncement =
|
||||
getId(STRING, "video_quality_unavailable_announcement")
|
||||
VoiceSearch = getId(ID, "voice_search")
|
||||
|
Reference in New Issue
Block a user