mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-03 08:04:36 +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:
parent
5208e50817
commit
b9ef6ea3bf
@ -14,7 +14,7 @@ import app.revanced.patches.shared.litho.fingerprints.EmptyComponentsFingerprint
|
|||||||
import app.revanced.patches.shared.litho.fingerprints.LithoFilterPatchConstructorFingerprint
|
import app.revanced.patches.shared.litho.fingerprints.LithoFilterPatchConstructorFingerprint
|
||||||
import app.revanced.patches.shared.litho.fingerprints.PathBuilderFingerprint
|
import app.revanced.patches.shared.litho.fingerprints.PathBuilderFingerprint
|
||||||
import app.revanced.patches.shared.litho.fingerprints.SetByteBufferFingerprint
|
import app.revanced.patches.shared.litho.fingerprints.SetByteBufferFingerprint
|
||||||
import app.revanced.util.getEmptyStringInstructionIndex
|
import app.revanced.util.getStringInstructionIndex
|
||||||
import app.revanced.util.getTargetIndex
|
import app.revanced.util.getTargetIndex
|
||||||
import app.revanced.util.getTargetIndexReversed
|
import app.revanced.util.getTargetIndexReversed
|
||||||
import app.revanced.util.getTargetIndexWithFieldReferenceType
|
import app.revanced.util.getTargetIndexWithFieldReferenceType
|
||||||
@ -140,7 +140,7 @@ object LithoFilterPatch : BytecodePatch(
|
|||||||
val stringBuilderRegister =
|
val stringBuilderRegister =
|
||||||
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
|
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
|
||||||
|
|
||||||
val emptyStringIndex = getEmptyStringInstructionIndex()
|
val emptyStringIndex = getStringInstructionIndex("")
|
||||||
|
|
||||||
val identifierIndex = getTargetIndexReversed(emptyStringIndex, Opcode.IPUT_OBJECT)
|
val identifierIndex = getTargetIndexReversed(emptyStringIndex, Opcode.IPUT_OBJECT)
|
||||||
val identifierRegister =
|
val identifierRegister =
|
||||||
|
@ -8,13 +8,14 @@ import app.revanced.patches.shared.transformation.IMethodCall
|
|||||||
import app.revanced.patches.shared.transformation.Instruction35cInfo
|
import app.revanced.patches.shared.transformation.Instruction35cInfo
|
||||||
import app.revanced.patches.shared.transformation.filterMapInstruction35c
|
import app.revanced.patches.shared.transformation.filterMapInstruction35c
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
|
||||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
abstract class BaseSpoofUserAgentPatch(
|
abstract class BaseSpoofUserAgentPatch(
|
||||||
private val packageName: String
|
private val packageName: String
|
||||||
@ -36,45 +37,39 @@ abstract class BaseSpoofUserAgentPatch(
|
|||||||
|
|
||||||
// Replace the result of context.getPackageName(), if it is used in a user agent string.
|
// Replace the result of context.getPackageName(), if it is used in a user agent string.
|
||||||
mutableMethod.apply {
|
mutableMethod.apply {
|
||||||
var isTargetMethod = true
|
// After context.getPackageName() the result is moved to a register.
|
||||||
|
val targetRegister = (
|
||||||
|
getInstruction(instructionIndex + 1)
|
||||||
|
as? OneRegisterInstruction ?: return
|
||||||
|
).registerA
|
||||||
|
|
||||||
for ((index, instruction) in implementation!!.instructions.withIndex()) {
|
// IndexOutOfBoundsException is possible here,
|
||||||
if (instruction.opcode != Opcode.CONST_STRING)
|
// but no such occurrences are present in the app.
|
||||||
continue
|
val referee =
|
||||||
|
getInstruction(instructionIndex + 2).getReference<MethodReference>()?.toString()
|
||||||
|
|
||||||
val constString = getInstruction<BuilderInstruction21c>(index).reference.toString()
|
// Only replace string builder usage.
|
||||||
|
if (referee != USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE) {
|
||||||
if (constString != "android.resource://" && constString != "gcore_")
|
return
|
||||||
continue
|
|
||||||
|
|
||||||
isTargetMethod = false
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTargetMethod) {
|
// Do not change the package name in methods that use resources, or for methods that use GmsCore.
|
||||||
// After context.getPackageName() the result is moved to a register.
|
// Changing these package names will result in playback limitations,
|
||||||
val targetRegister = (
|
// particularly Android VR background audio only playback.
|
||||||
getInstruction(instructionIndex + 1)
|
val resourceOrGmsStringInstructionIndex = indexOfFirstInstruction {
|
||||||
as? OneRegisterInstruction ?: return
|
val reference = getReference<StringReference>()
|
||||||
).registerA
|
opcode == Opcode.CONST_STRING &&
|
||||||
|
(reference?.string == "android.resource://" || reference?.string == "gcore_")
|
||||||
// IndexOutOfBoundsException is not possible here,
|
|
||||||
// but no such occurrences are present in the app.
|
|
||||||
val referee =
|
|
||||||
getInstruction(instructionIndex + 2).getReference<MethodReference>()?.toString()
|
|
||||||
|
|
||||||
// This can technically also match non-user agent string builder append methods,
|
|
||||||
// but no such occurrences are present in the app.
|
|
||||||
if (referee != "Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the result of context.getPackageName() with the original package name.
|
|
||||||
replaceInstruction(
|
|
||||||
instructionIndex + 1,
|
|
||||||
"const-string v$targetRegister, \"$packageName\"",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if (resourceOrGmsStringInstructionIndex >= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the result of context.getPackageName() with the original package name.
|
||||||
|
replaceInstruction(
|
||||||
|
instructionIndex + 1,
|
||||||
|
"const-string v$targetRegister, \"$packageName\"",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,4 +87,9 @@ abstract class BaseSpoofUserAgentPatch(
|
|||||||
"Ljava/lang/String;",
|
"Ljava/lang/String;",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE =
|
||||||
|
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
|
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
|
||||||
import app.revanced.patches.youtube.utils.compatibility.Constants
|
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.BuildInitPlaybackRequestFingerprint
|
||||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildPlayerRequestURIFingerprint
|
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.CreatePlayerRequestBodyFingerprint
|
||||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.NerdsStatsVideoFormatBuilderFingerprint
|
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.PlayerGestureConfigSyntheticFingerprint
|
||||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.SetPlayerRequestClientTypeFingerprint
|
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.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.utils.settings.SettingsPatch
|
||||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.getStringInstructionIndex
|
import app.revanced.util.getStringInstructionIndex
|
||||||
import app.revanced.util.getWalkerMethod
|
import app.revanced.util.getWalkerMethod
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.patch.BaseBytecodePatch
|
import app.revanced.util.patch.BaseBytecodePatch
|
||||||
import app.revanced.util.resultOrThrow
|
import app.revanced.util.resultOrThrow
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
@ -42,11 +45,11 @@ object SpoofClientPatch : BaseBytecodePatch(
|
|||||||
name = "Spoof client",
|
name = "Spoof client",
|
||||||
description = "Adds options to spoof the client to allow video playback.",
|
description = "Adds options to spoof the client to allow video playback.",
|
||||||
dependencies = setOf(
|
dependencies = setOf(
|
||||||
PlayerTypeHookPatch::class,
|
|
||||||
PlayerResponseMethodHookPatch::class,
|
PlayerResponseMethodHookPatch::class,
|
||||||
SettingsPatch::class,
|
SettingsPatch::class,
|
||||||
VideoInformationPatch::class,
|
VideoInformationPatch::class,
|
||||||
SpoofUserAgentPatch::class,
|
SpoofUserAgentPatch::class,
|
||||||
|
SharedResourceIdPatch::class,
|
||||||
),
|
),
|
||||||
compatiblePackages = Constants.COMPATIBLE_PACKAGE,
|
compatiblePackages = Constants.COMPATIBLE_PACKAGE,
|
||||||
fingerprints = setOf(
|
fingerprints = setOf(
|
||||||
@ -60,6 +63,10 @@ object SpoofClientPatch : BaseBytecodePatch(
|
|||||||
// Player gesture config.
|
// Player gesture config.
|
||||||
PlayerGestureConfigSyntheticFingerprint,
|
PlayerGestureConfigSyntheticFingerprint,
|
||||||
|
|
||||||
|
// Player speed menu item.
|
||||||
|
CreatePlaybackSpeedMenuItemFingerprint,
|
||||||
|
PlaybackRateBottomSheetBuilderFingerprint,
|
||||||
|
|
||||||
// Nerds stats video format.
|
// Nerds stats video format.
|
||||||
NerdsStatsVideoFormatBuilderFingerprint,
|
NerdsStatsVideoFormatBuilderFingerprint,
|
||||||
)
|
)
|
||||||
@ -257,26 +264,74 @@ object SpoofClientPatch : BaseBytecodePatch(
|
|||||||
// region fix player gesture.
|
// region fix player gesture.
|
||||||
|
|
||||||
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
||||||
arrayOf(3, 9).forEach { offSet ->
|
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
||||||
it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex - offSet)
|
val downAndOutLandscapeAllowedIndex = endIndex - 3
|
||||||
.apply {
|
val downAndOutPortraitAllowedIndex = endIndex - 9
|
||||||
val index = implementation!!.instructions.size - 1
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
arrayOf(
|
||||||
index,
|
downAndOutLandscapeAllowedIndex,
|
||||||
"""
|
downAndOutPortraitAllowedIndex,
|
||||||
invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
|
).forEach { index ->
|
||||||
move-result v$register
|
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
|
// 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 {
|
NerdsStatsVideoFormatBuilderFingerprint.resultOrThrow().mutableMethod.apply {
|
||||||
for (index in implementation!!.instructions.size - 1 downTo 0) {
|
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
|
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
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.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
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
|
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.
|
* TODO: Remove this annotation if support for YouTube v18.29.38 to v18.32.39 is dropped.
|
||||||
*/
|
*/
|
||||||
@FuzzyPatternScanMethod(5)
|
@FuzzyPatternScanMethod(5)
|
||||||
@ -30,28 +28,28 @@ internal object PlayerGestureConfigSyntheticFingerprint : MethodFingerprint(
|
|||||||
Opcode.IGET_OBJECT,
|
Opcode.IGET_OBJECT,
|
||||||
Opcode.INVOKE_INTERFACE,
|
Opcode.INVOKE_INTERFACE,
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed
|
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed.
|
||||||
Opcode.MOVE_RESULT,
|
Opcode.MOVE_RESULT,
|
||||||
Opcode.CHECK_CAST,
|
Opcode.CHECK_CAST,
|
||||||
Opcode.IPUT_BOOLEAN,
|
Opcode.IPUT_BOOLEAN,
|
||||||
Opcode.INVOKE_INTERFACE,
|
Opcode.INVOKE_INTERFACE,
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed
|
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed.
|
||||||
Opcode.MOVE_RESULT,
|
Opcode.MOVE_RESULT,
|
||||||
Opcode.IPUT_BOOLEAN,
|
Opcode.IPUT_BOOLEAN,
|
||||||
Opcode.RETURN_VOID,
|
Opcode.RETURN_VOID,
|
||||||
),
|
),
|
||||||
customFingerprint = { methodDef, classDef ->
|
customFingerprint = { methodDef, classDef ->
|
||||||
// This method is always called "a" because this kind of class always has a single method.
|
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
|
||||||
methodDef.name == "a" && classDef.methods.count() == 2 &&
|
methodDef.indexOfFirstInstruction {
|
||||||
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
|
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.parameterTypes.isEmpty() &&
|
||||||
reference.returnType == "Z"
|
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.data.BytecodeContext
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
import app.revanced.patcher.patch.PatchException
|
|
||||||
import app.revanced.patcher.patch.annotation.Patch
|
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.flyoutmenu.fingerprints.VideoQualityBottomSheetClassFingerprint
|
||||||
import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH
|
import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH
|
||||||
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
|
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
|
||||||
@ -17,7 +16,7 @@ import app.revanced.util.resultOrThrow
|
|||||||
)
|
)
|
||||||
object FlyoutMenuHookPatch : BytecodePatch(
|
object FlyoutMenuHookPatch : BytecodePatch(
|
||||||
setOf(
|
setOf(
|
||||||
PlaybackRateBottomSheetClassFingerprint,
|
PlaybackRateBottomSheetBuilderFingerprint,
|
||||||
VideoQualityBottomSheetClassFingerprint
|
VideoQualityBottomSheetClassFingerprint
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -30,17 +29,12 @@ object FlyoutMenuHookPatch : BytecodePatch(
|
|||||||
INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR
|
INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR
|
||||||
)!!.mutableClass
|
)!!.mutableClass
|
||||||
|
|
||||||
PlaybackRateBottomSheetClassFingerprint.resultOrThrow().let {
|
PlaybackRateBottomSheetBuilderFingerprint.resultOrThrow().let {
|
||||||
it.mutableMethod.apply {
|
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 =
|
val smaliInstructions =
|
||||||
"""
|
"""
|
||||||
if-eqz v0, :ignore
|
if-eqz v0, :ignore
|
||||||
invoke-virtual {v0}, $definingClass->$playbackRateBottomSheetBuilderMethodName()V
|
invoke-virtual {v0}, $definingClass->$name()V
|
||||||
:ignore
|
:ignore
|
||||||
return-void
|
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 TotalTime = -1L
|
||||||
var TouchArea = -1L
|
var TouchArea = -1L
|
||||||
var VideoQualityBottomSheet = -1L
|
var VideoQualityBottomSheet = -1L
|
||||||
|
var VarispeedUnavailableTitle = -1L
|
||||||
var VideoQualityUnavailableAnnouncement = -1L
|
var VideoQualityUnavailableAnnouncement = -1L
|
||||||
var VoiceSearch = -1L
|
var VoiceSearch = -1L
|
||||||
var YouTubeControlsOverlaySubtitleButton = -1L
|
var YouTubeControlsOverlaySubtitleButton = -1L
|
||||||
@ -187,6 +188,7 @@ object SharedResourceIdPatch : ResourcePatch() {
|
|||||||
TotalTime = getId(STRING, "total_time")
|
TotalTime = getId(STRING, "total_time")
|
||||||
TouchArea = getId(ID, "touch_area")
|
TouchArea = getId(ID, "touch_area")
|
||||||
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
|
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
|
||||||
|
VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title")
|
||||||
VideoQualityUnavailableAnnouncement =
|
VideoQualityUnavailableAnnouncement =
|
||||||
getId(STRING, "video_quality_unavailable_announcement")
|
getId(STRING, "video_quality_unavailable_announcement")
|
||||||
VoiceSearch = getId(ID, "voice_search")
|
VoiceSearch = getId(ID, "voice_search")
|
||||||
|
@ -195,6 +195,73 @@ fun BytecodeContext.literalInstructionHook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index of the first instruction with the literal value or throw a [PatchException].
|
||||||
|
*
|
||||||
|
* @throws [PatchException] if the literal index not found.
|
||||||
|
*/
|
||||||
|
fun Method.indexOfWideLiteralInstructionOrThrow(literal: Long): Int {
|
||||||
|
val index = getWideLiteralInstructionIndex(literal)
|
||||||
|
if (index < 0) {
|
||||||
|
val value =
|
||||||
|
if (literal >= 2130706432) // 0x7f000000, general resource id
|
||||||
|
String.format("%#X", literal).lowercase()
|
||||||
|
else
|
||||||
|
literal.toString()
|
||||||
|
|
||||||
|
throw PatchException("Found literal value for: '$value' but method does not contain the id: $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
|
||||||
|
*
|
||||||
|
* @param startIndex Optional starting index to start searching from.
|
||||||
|
* @return -1 if the instruction is not found.
|
||||||
|
* @see indexOfFirstInstructionOrThrow
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstInstruction(startIndex: Int = 0, predicate: Instruction.() -> Boolean): Int {
|
||||||
|
val index = this.implementation!!.instructions.drop(startIndex).indexOfFirst(predicate)
|
||||||
|
|
||||||
|
return if (index >= 0) {
|
||||||
|
startIndex + index
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
|
||||||
|
*
|
||||||
|
* @return the index of the instruction
|
||||||
|
* @throws PatchException
|
||||||
|
* @see indexOfFirstInstruction
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, predicate: Instruction.() -> Boolean): Int {
|
||||||
|
val index = indexOfFirstInstruction(startIndex, predicate)
|
||||||
|
if (index < 0) {
|
||||||
|
throw PatchException("Could not find instruction index")
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of indices of the opcode in reverse order.
|
||||||
|
*/
|
||||||
|
fun Method.findOpcodeIndicesReversed(opcode: Opcode): List<Int> {
|
||||||
|
val indexes = implementation!!.instructions
|
||||||
|
.withIndex()
|
||||||
|
.filter { (_, instruction) -> instruction.opcode == opcode }
|
||||||
|
.map { (index, _) -> index }
|
||||||
|
.reversed()
|
||||||
|
|
||||||
|
if (indexes.isEmpty()) throw PatchException("No ${opcode.name} instructions found in: $this")
|
||||||
|
|
||||||
|
return indexes
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the first wide literal instruction with the given value.
|
* Find the index of the first wide literal instruction with the given value.
|
||||||
*
|
*
|
||||||
@ -206,8 +273,6 @@ fun Method.getWideLiteralInstructionIndex(literal: Long) = implementation?.let {
|
|||||||
}
|
}
|
||||||
} ?: -1
|
} ?: -1
|
||||||
|
|
||||||
fun Method.getEmptyStringInstructionIndex() = getStringInstructionIndex("")
|
|
||||||
|
|
||||||
fun Method.getStringInstructionIndex(value: String) = implementation?.let {
|
fun Method.getStringInstructionIndex(value: String) = implementation?.let {
|
||||||
it.instructions.indexOfFirst { instruction ->
|
it.instructions.indexOfFirst { instruction ->
|
||||||
instruction.opcode == Opcode.CONST_STRING
|
instruction.opcode == Opcode.CONST_STRING
|
||||||
|
@ -1496,8 +1496,7 @@ Limitation: Feed videos will play for less than 1 minute before encountering pla
|
|||||||
|
|
||||||
Side effects include:
|
Side effects include:
|
||||||
• No HDR video.
|
• No HDR video.
|
||||||
• Playback speed menu is missing.
|
• Higher video qualities may not be available.
|
||||||
• Higher video qualities may be missing.
|
|
||||||
• Watch history does not work with a brand account.
|
• Watch history does not work with a brand account.
|
||||||
• Live streams cannot play as audio-only.
|
• Live streams cannot play as audio-only.
|
||||||
• Live streams not available on Android 8.0."</string>
|
• Live streams not available on Android 8.0."</string>
|
||||||
@ -1506,14 +1505,14 @@ Side effects include:
|
|||||||
|
|
||||||
Side effects include:
|
Side effects include:
|
||||||
• No HDR video.
|
• No HDR video.
|
||||||
• Audio track menu and playback speed menu are missing.
|
• Audio track menu is missing.
|
||||||
• Captions may not be available."</string>
|
• Captions may not be available."</string>
|
||||||
<string name="revanced_spoof_client_use_android_tv_title">Android TV</string>
|
<string name="revanced_spoof_client_use_android_tv_title">Android TV</string>
|
||||||
<string name="revanced_spoof_client_use_android_tv_summary">"Spoof client to Android TV (YouTube TV).
|
<string name="revanced_spoof_client_use_android_tv_summary">"Spoof client to Android TV (YouTube TV).
|
||||||
|
|
||||||
Side effects include:
|
Side effects include:
|
||||||
• No HDR video.
|
• No HDR video.
|
||||||
• Audio track menu and playback speed menu are missing.
|
• Audio track menu is missing.
|
||||||
• Captions may not be available.
|
• Captions may not be available.
|
||||||
• Some live streams are not supported for playback."</string>
|
• Some live streams are not supported for playback."</string>
|
||||||
<string name="revanced_spoof_client_use_android_vr_title">Android VR</string>
|
<string name="revanced_spoof_client_use_android_vr_title">Android VR</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user