feat(YouTube): Add support version 20.02.38

This commit is contained in:
inotia00 2025-01-17 14:58:20 +09:00
parent 316038cd75
commit c8cc8c4bda
22 changed files with 358 additions and 178 deletions

View File

@ -61,6 +61,10 @@ public class ShortsPatch {
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
}
public static boolean disableResumingStartupShortsPlayer(boolean original) {
return !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get() && original;
}
public static boolean enableShortsTimeStamp(boolean original) {
return ENABLE_TIME_STAMP || original;
}

View File

@ -16,6 +16,7 @@ import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstStringInstruction
import app.revanced.util.indexOfFirstStringInstructionOrThrow
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
@ -27,6 +28,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.util.MethodUtil
@ -42,6 +44,8 @@ private var filterCount = 0
internal lateinit var addLithoFilter: (String) -> Unit
private set
internal var emptyComponentLabel = ""
val lithoFilterPatch = bytecodePatch(
description = "lithoFilterPatch",
) {
@ -73,6 +77,8 @@ val lithoFilterPatch = bytecodePatch(
return-object v0
"""
emptyComponentLabel = label
Pair(this, label)
}
}
@ -121,17 +127,25 @@ val lithoFilterPatch = bytecodePatch(
val stringBuilderRegister =
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
val emptyStringIndex = indexOfFirstStringInstructionOrThrow("")
val emptyStringIndex = indexOfFirstStringInstruction("")
val relativeIndex = if (emptyStringIndex > -1) {
emptyStringIndex
} else {
val separatorIndex = indexOfFirstStringInstructionOrThrow("|")
indexOfFirstInstructionOrThrow(separatorIndex) {
opcode == Opcode.NEW_INSTANCE &&
getReference<TypeReference>()?.type == "Ljava/lang/StringBuilder;"
}
}
val identifierRegister = getInstruction<TwoRegisterInstruction>(
indexOfFirstInstructionReversedOrThrow(emptyStringIndex) {
indexOfFirstInstructionReversedOrThrow(relativeIndex) {
opcode == Opcode.IPUT_OBJECT
&& getReference<FieldReference>()?.type == "Ljava/lang/String;"
}
).registerA
val objectRegister = getInstruction<FiveRegisterInstruction>(
indexOfFirstInstructionOrThrow(emptyStringIndex) {
opcode == Opcode.INVOKE_VIRTUAL
}
indexOfFirstInstructionOrThrow(relativeIndex, Opcode.INVOKE_VIRTUAL)
).registerC
val insertIndex = stringBuilderIndex + 1

View File

@ -16,6 +16,7 @@ import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow
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.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@ -40,11 +41,11 @@ val textComponentPatch = bytecodePatch(
spannedIndex = indexOfSpannableStringInstruction(this)
spannedRegister = getInstruction<FiveRegisterInstruction>(spannedIndex).registerC
spannedContextRegister =
getInstruction<TwoRegisterInstruction>(0).registerA
getInstruction<OneRegisterInstruction>(spannedIndex + 1).registerA
replaceInstruction(
spannedIndex,
"nop"
"move-object/from16 v$spannedContextRegister, p0"
)
addInstruction(
++spannedIndex,

View File

@ -10,6 +10,7 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.emptyComponentLabel
import app.revanced.patches.shared.mainactivity.onCreateMethod
import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
@ -21,6 +22,9 @@ import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_FEED_COMPONENTS
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.bar
import app.revanced.patches.youtube.utils.resourceid.captionToggleContainer
import app.revanced.patches.youtube.utils.resourceid.channelListSubMenu
@ -78,6 +82,7 @@ val feedComponentsPatch = bytecodePatch(
sharedResourceIdPatch,
settingsPatch,
bottomSheetHookPatch,
versionCheckPatch,
)
execute {
@ -223,19 +228,22 @@ val feedComponentsPatch = bytecodePatch(
// region patch for hide subscriptions channel section for tablet
arrayOf(
channelListSubMenuTabletFingerprint,
channelListSubMenuTabletSyntheticFingerprint
).forEach { fingerprint ->
fingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(0))
)
// Integrated as a litho component since YouTube 20.02.
if (!is_20_02_or_greater) {
arrayOf(
channelListSubMenuTabletFingerprint,
channelListSubMenuTabletSyntheticFingerprint
).forEach { fingerprint ->
fingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(0))
)
}
}
}
@ -287,29 +295,27 @@ val feedComponentsPatch = bytecodePatch(
it.method.apply {
val freeRegister = implementation!!.registerCount - parameters.size - 2
val insertIndex = indexOfFirstInstructionOrThrow {
val reference = ((this as? ReferenceInstruction)?.reference as? MethodReference)
val reference = getReference<MethodReference>()
reference?.parameterTypes?.size == 1 &&
reference.parameterTypes.first() == "[B" &&
reference.returnType.startsWith("L")
}
val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT)
val objectOpcode = if (is_18_34_or_greater)
Opcode.IGET_OBJECT
else
Opcode.MOVE_OBJECT
val objectIndex = indexOfFirstInstructionReversedOrThrow(insertIndex, objectOpcode)
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
val jumpIndex = it.patternMatch!!.startIndex
addInstructionsWithLabels(
insertIndex, """
invoke-static {v$objectRegister, v$freeRegister}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
invoke-static {v$objectRegister, p3}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
move-result v$freeRegister
if-nez v$freeRegister, :filter
""", ExternalLabel("filter", getInstruction(jumpIndex))
)
addInstruction(
0,
"move-object/from16 v$freeRegister, p3"
if-eqz v$freeRegister, :ignore
""" + emptyComponentLabel,
ExternalLabel("ignore", getInstruction(insertIndex))
)
}
}

View File

@ -87,7 +87,6 @@ internal val floatingMicrophoneFingerprint = legacyFingerprint(
opcodes = listOf(
Opcode.IGET_BOOLEAN,
Opcode.IF_EQZ,
Opcode.RETURN_VOID
),
literals = listOf(fab),
)

View File

@ -20,6 +20,7 @@ import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_LAYOUT_COMPONENTS
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.accountSwitcherAccessibility
import app.revanced.patches.youtube.utils.resourceid.fab
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
@ -154,18 +155,17 @@ val layoutComponentsPatch = bytecodePatch(
// region patch for hide floating microphone
floatingMicrophoneFingerprint.matchOrThrow().let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
floatingMicrophoneFingerprint.methodOrThrow().apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(fab)
val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
val insertRegister = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
addInstructions(
insertIndex + 1, """
invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z
move-result v$register
"""
)
}
addInstructions(
booleanIndex + 1, """
invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z
move-result v$insertRegister
"""
)
}
// endregion

View File

@ -15,6 +15,7 @@ import app.revanced.patches.youtube.utils.castbutton.hookToolBarCastButton
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.patch.PatchList.TOOLBAR_COMPONENTS
import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.actionBarRingoBackground
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
@ -265,7 +266,11 @@ val toolBarComponentsPatch = bytecodePatch(
// region patch for hide search term thumbnail
createSearchSuggestionsFingerprint.methodOrThrow().apply {
val relativeIndex = indexOfFirstLiteralInstructionOrThrow(40L)
val literal = if (is_19_46_or_greater)
32L
else
40L
val relativeIndex = indexOfFirstLiteralInstructionOrThrow(literal)
val replaceIndex = indexOfFirstInstructionReversedOrThrow(relativeIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V"

View File

@ -23,6 +23,8 @@ import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH
import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.suggestedVideoEndScreenPatch
import app.revanced.patches.youtube.utils.patch.PatchList.PLAYER_COMPONENTS
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.darkBackground
import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast
import app.revanced.patches.youtube.utils.resourceid.scrimOverlay
@ -278,9 +280,15 @@ val playerComponentsPatch = bytecodePatch(
speedOverlayPatch,
suggestedVideoEndScreenPatch,
videoInformationPatch,
versionCheckPatch,
)
execute {
var settingArray = arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: PLAYER_COMPONENTS"
)
fun MutableMethod.getAllLiteralComponent(
startIndex: Int,
endIndex: Int
@ -563,30 +571,34 @@ val playerComponentsPatch = bytecodePatch(
)
}
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
val insertIndex =
indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub)
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
if (!is_20_02_or_greater) {
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
val insertIndex =
indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub)
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setOnClickListener"
}
val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1)
val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setOnClickListener"
}
val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1)
if (constComponent.isNotEmpty()) {
addInstruction(
onClickListenerIndex + 2,
constComponent
if (constComponent.isNotEmpty()) {
addInstruction(
onClickListenerIndex + 2,
constComponent
)
}
addInstructionsWithLabels(
insertIndex, """
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideSeekUndoMessage()Z
move-result v$insertRegister
if-nez v$insertRegister, :default
""", ExternalLabel("default", getInstruction(onClickListenerIndex + 1))
)
settingArray += "SETTINGS: HIDE_SEEK_UNDO_MESSAGE"
}
addInstructionsWithLabels(
insertIndex, """
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideSeekUndoMessage()Z
move-result v$insertRegister
if-nez v$insertRegister, :default
""", ExternalLabel("default", getInstruction(onClickListenerIndex + 1))
)
}
// endregion
@ -652,10 +664,7 @@ val playerComponentsPatch = bytecodePatch(
// region add settings
addPreference(
arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: PLAYER_COMPONENTS"
),
settingArray,
PLAYER_COMPONENTS
)

View File

@ -7,6 +7,7 @@ import app.revanced.patches.youtube.utils.resourceid.quickActionsElementContaine
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.util.MethodUtil
internal val broadcastReceiverFingerprint = legacyFingerprint(
name = "broadcastReceiverFingerprint",
@ -45,12 +46,15 @@ internal val playerTitleViewFingerprint = legacyFingerprint(
literals = listOf(playerVideoTitleView),
)
internal val quickActionsElementFingerprint = legacyFingerprint(
name = "quickActionsElementFingerprint",
internal val quickActionsElementSyntheticFingerprint = legacyFingerprint(
name = "quickActionsElementSyntheticFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;"),
literals = listOf(quickActionsElementContainer),
customFingerprint = { _, classDef ->
AccessFlags.SYNTHETIC.isSet(classDef.accessFlags)
}
)
internal val relatedEndScreenResultsFingerprint = legacyFingerprint(

View File

@ -19,6 +19,7 @@ import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
import app.revanced.patches.youtube.utils.fullscreen.fullscreenButtonHookPatch
import app.revanced.patches.youtube.utils.indexOfFocusableInTouchModeInstruction
import app.revanced.patches.youtube.utils.layoutConstructorFingerprint
import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
import app.revanced.patches.youtube.utils.patch.PatchList.FULLSCREEN_COMPONENTS
@ -101,7 +102,6 @@ val fullscreenComponentsPatch = bytecodePatch(
"invoke-static {v$targetRegister}, " +
"$PLAYER_CLASS_DESCRIPTOR->disableEngagementPanels(Landroidx/coordinatorlayout/widget/CoordinatorLayout;)V"
)
}
playerTitleViewFingerprint.methodOrThrow().apply {
@ -190,7 +190,7 @@ val fullscreenComponentsPatch = bytecodePatch(
// region patch for quick actions
quickActionsElementFingerprint.methodOrThrow().apply {
quickActionsElementSyntheticFingerprint.methodOrThrow().apply {
val containerCalls = implementation!!.instructions.withIndex()
.filter { instruction ->
(instruction.value as? WideLiteralInstruction)?.wideLiteral == quickActionsElementContainer
@ -219,11 +219,13 @@ val fullscreenComponentsPatch = bytecodePatch(
// region patch for compact control overlay
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setFocusableInTouchMode"
val targetIndex = indexOfFocusableInTouchModeInstruction(this)
val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex) {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_STATIC &&
reference?.returnType == "Z" &&
reference.parameterTypes.size == 1
}
val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex, Opcode.INVOKE_STATIC)
val walkerMethod = getWalkerMethod(walkerIndex)
walkerMethod.apply {

View File

@ -10,7 +10,7 @@ import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
/**
* This fingerprint is compatible with all YouTube versions after v18.15.40.
* YouTube v18.15.40 ~ YouTube 19.46.42
*/
internal val userWasInShortsABConfigFingerprint = legacyFingerprint(
name = "userWasInShortsABConfigFingerprint",
@ -27,6 +27,20 @@ internal fun indexOfOptionalInstruction(method: Method) =
getReference<MethodReference>().toString() == "Lj${'$'}/util/Optional;->of(Ljava/lang/Object;)Lj${'$'}/util/Optional;"
}
/**
* YouTube 19.47.53 ~
*/
internal val userWasInShortsABConfigAlternativeFingerprint = legacyFingerprint(
name = "userWasInShortsABConfigAlternativeFingerprint",
returnType = "V",
parameters = listOf("I"),
opcodes = listOf(Opcode.OR_INT_LIT8),
strings = listOf("alias", "null"),
)
/**
* ~ YouTube 19.50.42
*/
internal val userWasInShortsFingerprint = legacyFingerprint(
name = "userWasInShortsFingerprint",
returnType = "V",
@ -34,3 +48,14 @@ internal val userWasInShortsFingerprint = legacyFingerprint(
parameters = listOf("Ljava/lang/Object;"),
strings = listOf("Failed to read user_was_in_shorts proto after successful warmup")
)
/**
* YouTube 20.02.08 ~
*/
internal val userWasInShortsAlternativeFingerprint = legacyFingerprint(
name = "userWasInShortsAlternativeFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Ljava/lang/Object;"),
strings = listOf("userIsInShorts: ")
)

View File

@ -1,20 +1,28 @@
package app.revanced.patches.youtube.shorts.startupshortsreset
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.removeInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.patch.PatchList.DISABLE_RESUMING_SHORTS_ON_STARTUP
import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstStringInstructionOrThrow
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
@ -27,12 +35,14 @@ val resumingShortsOnStartupPatch = bytecodePatch(
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
dependsOn(
settingsPatch,
versionCheckPatch,
)
execute {
userWasInShortsABConfigFingerprint.methodOrThrow().apply {
val startIndex = indexOfOptionalInstruction(this)
fun MutableMethod.hookUserWasInShortsABConfig(startIndex: Int) {
val walkerIndex = implementation!!.instructions.let {
val subListIndex =
it.subList(startIndex, startIndex + 20).indexOfFirst { instruction ->
@ -64,29 +74,65 @@ val resumingShortsOnStartupPatch = bytecodePatch(
}
}
userWasInShortsFingerprint.methodOrThrow().apply {
val listenableInstructionIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" &&
getReference<MethodReference>()?.name == "isDone"
if (is_19_46_or_greater) {
userWasInShortsABConfigAlternativeFingerprint.methodOrThrow().apply {
val stringIndex = indexOfFirstStringInstructionOrThrow("null")
val startIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.OR_INT_LIT8)
hookUserWasInShortsABConfig(startIndex)
}
val originalInstructionRegister =
getInstruction<FiveRegisterInstruction>(listenableInstructionIndex).registerC
val freeRegister =
getInstruction<OneRegisterInstruction>(listenableInstructionIndex + 1).registerA
} else {
userWasInShortsABConfigFingerprint.methodOrThrow().apply {
val startIndex = indexOfOptionalInstruction(this)
hookUserWasInShortsABConfig(startIndex)
}
}
addInstructionsWithLabels(
listenableInstructionIndex + 1,
"""
invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
move-result v$freeRegister
if-eqz v$freeRegister, :show
return-void
:show
invoke-interface {v$originalInstructionRegister}, Lcom/google/common/util/concurrent/ListenableFuture;->isDone()Z
if (is_20_02_or_greater) {
userWasInShortsAlternativeFingerprint.matchOrThrow().let {
it.method.apply {
val stringIndex = it.stringMatches!!.first().index
val booleanValueIndex = indexOfFirstInstructionReversedOrThrow(stringIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "booleanValue"
}
val booleanValueRegister =
getInstruction<OneRegisterInstruction>(booleanValueIndex + 1).registerA
addInstructions(
booleanValueIndex + 2, """
invoke-static {v$booleanValueRegister}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer(Z)Z
move-result v$booleanValueRegister
"""
)
}
}
} else {
userWasInShortsFingerprint.methodOrThrow().apply {
val listenableInstructionIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_INTERFACE &&
reference?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" &&
reference.name == "isDone"
}
val originalInstructionRegister =
getInstruction<FiveRegisterInstruction>(listenableInstructionIndex).registerC
val freeRegister =
getInstruction<OneRegisterInstruction>(listenableInstructionIndex + 1).registerA
addInstructionsWithLabels(
listenableInstructionIndex + 1,
"""
)
removeInstruction(listenableInstructionIndex)
invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
move-result v$freeRegister
if-eqz v$freeRegister, :show
return-void
:show
invoke-interface {v$originalInstructionRegister}, Lcom/google/common/util/concurrent/ListenableFuture;->isDone()Z
"""
)
removeInstruction(listenableInstructionIndex)
}
}
// region add settings

View File

@ -27,14 +27,12 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val bottomSheetMenuItemBuilderFingerprint = legacyFingerprint(
name = "bottomSheetMenuItemBuilderFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "L",
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT
Opcode.IGET,
Opcode.AND_INT_LIT16,
Opcode.IF_EQZ,
),
strings = listOf("Text missing for BottomSheetMenuItem."),
customFingerprint = { method, _ ->
@ -251,9 +249,19 @@ internal val youtubeControlsOverlayFingerprint = legacyFingerprint(
fadeDurationFast,
insetOverlayViewLayout,
scrimOverlay,
seekUndoEduOverlayStub
// Removed in YouTube 20.02.38+
// seekUndoEduOverlayStub
),
customFingerprint = { method, _ ->
indexOfFocusableInTouchModeInstruction(method) >= 0
}
)
internal fun indexOfFocusableInTouchModeInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setFocusableInTouchMode"
}
const val PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR =
"Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"

View File

@ -15,7 +15,8 @@ internal object Constants {
"18.48.39", // This is the last version that do not use Rolling Number.
"19.05.36", // This is the last version with the least YouTube experimental flag.
"19.16.39", // This is the last version where the 'Restore old seekbar thumbnails' setting works.
"19.44.39", // This is the latest version supported by the RVX patch.
"19.44.39", // This is the last supported version for 2024.
"20.02.38", // This is the latest version supported by the RVX patch.
)
)
}

View File

@ -7,56 +7,91 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.util.addStaticFieldToExtension
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
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
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import kotlin.collections.mutableListOf
private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR =
"$EXTENSION_PATH/utils/VideoUtils;"
internal lateinit var enterFullscreenMethod: MutableMethod
internal var enterFullscreenMethods = mutableListOf<MutableMethod>()
val fullscreenButtonHookPatch = bytecodePatch(
description = "fullscreenButtonHookPatch"
) {
dependsOn(sharedExtensionPatch)
dependsOn(
sharedExtensionPatch,
versionCheckPatch,
)
execute {
val (referenceClass, fullscreenActionClass) = with(
nextGenWatchLayoutFullscreenModeFingerprint.methodOrThrow()
) {
val targetIndex = indexOfFirstInstructionReversedOrThrow {
opcode == Opcode.INVOKE_DIRECT &&
getReference<MethodReference>()?.parameterTypes?.size == 2
}
val targetReference =
getInstruction<ReferenceInstruction>(targetIndex).reference as MethodReference
fun getParameters(): Pair<MutableMethod, String> {
nextGenWatchLayoutFullscreenModeFingerprint.methodOrThrow().apply {
val methodIndex = indexOfFirstInstructionReversedOrThrow {
opcode == Opcode.INVOKE_DIRECT &&
getReference<MethodReference>()?.parameterTypes?.size == 2
}
val fieldIndex =
indexOfFirstInstructionReversedOrThrow(methodIndex, Opcode.IGET_OBJECT)
val fullscreenActionClass =
(getInstruction<ReferenceInstruction>(fieldIndex).reference as FieldReference).type
Pair(targetReference.definingClass, targetReference.parameterTypes[1].toString())
if (is_20_02_or_greater) {
val setAnimatorListenerIndex =
indexOfFirstInstructionOrThrow(methodIndex, Opcode.INVOKE_VIRTUAL)
getWalkerMethod(setAnimatorListenerIndex).apply {
val addListenerIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "addListener"
}
val animatorListenerAdapterClass = getInstruction<ReferenceInstruction>(
indexOfFirstInstructionReversedOrThrow(addListenerIndex, Opcode.NEW_INSTANCE)
).reference.toString()
return Pair(
findMethodOrThrow(animatorListenerAdapterClass) { parameters.isEmpty() },
fullscreenActionClass
)
}
} else {
val animatorListenerClass =
(getInstruction<ReferenceInstruction>(methodIndex).reference as MethodReference).definingClass
return Pair(
findMethodOrThrow(animatorListenerClass) { parameters == listOf("I") },
fullscreenActionClass
)
}
}
}
val (animatorListenerMethod, fullscreenActionClass) = getParameters()
val (enterFullscreenReference, exitFullscreenReference, opcodeName) =
with(findMethodOrThrow(referenceClass) { parameters == listOf("I") }) {
with(animatorListenerMethod) {
val enterFullscreenIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.returnType == "V" &&
reference.definingClass == fullscreenActionClass &&
reference.parameterTypes.size == 0
reference.parameterTypes.isEmpty()
}
val exitFullscreenIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
reference?.returnType == "V" &&
reference.definingClass == fullscreenActionClass &&
reference.parameterTypes.size == 0
reference.parameterTypes.isEmpty()
}
val enterFullscreenReference =
@ -68,18 +103,28 @@ val fullscreenButtonHookPatch = bytecodePatch(
val enterFullscreenClass =
(enterFullscreenReference as MethodReference).definingClass
enterFullscreenMethod = if (opcode == Opcode.INVOKE_INTERFACE) {
classes.find { classDef -> classDef.interfaces.contains(enterFullscreenClass) }
?.let { classDef ->
if (opcode == Opcode.INVOKE_INTERFACE) {
classes.forEach { classDef ->
if (enterFullscreenMethods.size >= 2)
return@forEach
if (!classDef.interfaces.contains(enterFullscreenClass))
return@forEach
val enterFullscreenMethod =
proxy(classDef)
.mutableClass
.methods
.find { method -> method.name == enterFullscreenReference.name }
} ?: throw PatchException("No matching classes: $enterFullscreenClass")
} else {
findMethodOrThrow(enterFullscreenClass) {
name == enterFullscreenReference.name
?: throw PatchException("No matching classes: $enterFullscreenClass")
enterFullscreenMethods.add(enterFullscreenMethod)
}
} else {
val enterFullscreenMethod =
findMethodOrThrow(enterFullscreenClass) {
name == enterFullscreenReference.name
}
enterFullscreenMethods.add(enterFullscreenMethod)
}
Triple(

View File

@ -9,7 +9,7 @@ import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
import app.revanced.patches.youtube.utils.fullscreen.enterFullscreenMethod
import app.revanced.patches.youtube.utils.fullscreen.enterFullscreenMethods
import app.revanced.patches.youtube.utils.fullscreen.fullscreenButtonHookPatch
import app.revanced.patches.youtube.utils.playerButtonsResourcesFingerprint
import app.revanced.patches.youtube.utils.playerButtonsVisibilityFingerprint
@ -122,10 +122,12 @@ private val playerControlsBytecodePatch = bytecodePatch(
// Reproduced only in RVX
if (is_19_23_or_greater) {
enterFullscreenMethod.addInstruction(
0,
"invoke-static {}, $EXTENSION_PLAYER_CONTROLS_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediately()V"
)
enterFullscreenMethods.forEach { method ->
method.addInstruction(
0,
"invoke-static {}, $EXTENSION_PLAYER_CONTROLS_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediately()V"
)
}
}
// endregion

View File

@ -51,6 +51,8 @@ var is_19_44_or_greater = false
private set
var is_19_46_or_greater = false
private set
var is_20_02_or_greater = false
private set
val versionCheckPatch = resourcePatch(
description = "versionCheckPatch",
@ -89,5 +91,6 @@ val versionCheckPatch = resourcePatch(
is_19_43_or_greater = 244405000 <= playStoreServicesVersion
is_19_44_or_greater = 244505000 <= playStoreServicesVersion
is_19_46_or_greater = 244705000 <= playStoreServicesVersion
is_20_02_or_greater = 250299000 <= playStoreServicesVersion
}
}

View File

@ -2,11 +2,9 @@ package app.revanced.patches.youtube.utils.settings
import app.revanced.patches.youtube.utils.resourceid.appearance
import app.revanced.util.fingerprint.legacyFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val themeSetterSystemFingerprint = legacyFingerprint(
name = "themeSetterSystemFingerprint",
returnType = "L",
opcodes = listOf(Opcode.RETURN_OBJECT),
literals = listOf(appearance),
)

View File

@ -1,12 +1,9 @@
package app.revanced.patches.youtube.utils.settings
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.extension.Constants.EXTENSION_UTILS_CLASS_DESCRIPTOR
import app.revanced.patches.shared.extension.Constants.EXTENSION_UTILS_PATH
import app.revanced.patches.shared.mainactivity.injectConstructorMethodCall
@ -23,11 +20,14 @@ import app.revanced.patches.youtube.utils.patch.PatchList.SETTINGS_FOR_YOUTUBE
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.removeStringsElements
import app.revanced.util.valueOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import org.w3c.dom.Element
import java.nio.file.Files
@ -50,23 +50,16 @@ private val settingsBytecodePatch = bytecodePatch(
)
execute {
fun MutableMethod.injectCall(index: Int) {
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1, """
invoke-static {v$register}, $EXTENSION_THEME_METHOD_DESCRIPTOR
return-object v$register
"""
)
removeInstruction(index)
}
// apply the current theme of the settings page
themeSetterSystemFingerprint.matchOrThrow().let {
it.method.apply {
injectCall(implementation!!.instructions.size - 1)
injectCall(it.patternMatch!!.startIndex)
themeSetterSystemFingerprint.methodOrThrow().apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructionsAtControlFlowLabel(
index,
"invoke-static { v$register }, $EXTENSION_THEME_METHOD_DESCRIPTOR"
)
}
}

View File

@ -249,11 +249,13 @@ val videoInformationPatch = bytecodePatch(
)
val literalIndex = indexOfFirstLiteralInstructionOrThrow(45368273L)
val walkerIndex =
indexOfFirstInstructionReversedOrThrow(
literalIndex,
Opcode.INVOKE_VIRTUAL_RANGE
)
val walkerIndex = indexOfFirstInstructionReversedOrThrow(literalIndex) {
val reference = getReference<MethodReference>()
(opcode == Opcode.INVOKE_VIRTUAL || opcode == Opcode.INVOKE_VIRTUAL_RANGE) &&
reference?.definingClass == definingClass &&
reference.parameterTypes.isEmpty() &&
reference.returnType == "V"
}
videoEndMethod = getWalkerMethod(walkerIndex)
}

View File

@ -13,20 +13,12 @@ private val PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST = listOf(
"I",
"I"
)
private val PLAYER_PARAMETER_ENDS_WITH_PARAMETER_LIST = listOf(
"Ljava/util/Set;",
"Ljava/lang/String;",
"Ljava/lang/String;",
"L",
"Z", // Appears to indicate if the video id is being opened or is currently playing.
"Z",
"Z"
)
internal val playerParameterBuilderFingerprint = legacyFingerprint(
name = "playerParameterBuilderFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "L",
strings = listOf("psps"),
// 19.22 and earlier parameters are:
// "Ljava/lang/String;", // VideoId.
// "[B",
@ -60,20 +52,41 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
customFingerprint = custom@{ method, _ ->
val parameterTypes = method.parameterTypes
val parameterSize = parameterTypes.size
if (parameterSize != 13 && parameterSize != 14) {
if (parameterSize < 13) {
return@custom false
}
val startsWithMethodParameterList = parameterTypes.slice(0..5)
val endsWithMethodParameterList = parameterTypes.slice(parameterSize - 7..<parameterSize)
parametersEqual(
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
startsWithMethodParameterList
) &&
parametersEqual(
PLAYER_PARAMETER_ENDS_WITH_PARAMETER_LIST,
endsWithMethodParameterList
)
)
}
)
/**
* For targets 19.22 and earlier.
*/
private val PLAYER_PARAMETER_LEGACY_LIST = listOf(
"Ljava/lang/String;", // VideoId.
"[B",
"Ljava/lang/String;", // Player parameters proto buffer.
"Ljava/lang/String;",
"I",
"I",
"Ljava/util/Set;",
"Ljava/lang/String;",
"Ljava/lang/String;",
"L",
"Z", // Appears to indicate if the video id is being opened or is currently playing.
"Z",
"Z",
)
internal val playerParameterBuilderLegacyFingerprint = legacyFingerprint(
name = "playerParameterBuilderLegacyFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "L",
parameters = PLAYER_PARAMETER_LEGACY_LIST,
)

View File

@ -35,7 +35,7 @@ val playerResponseMethodHookPatch = bytecodePatch(
execute {
playerParameterBuilderFingerprint.methodOrThrow().apply {
playerResponseMethod = this
parameterIsShortAndOpeningOrPlaying = parameters.size - 2
parameterIsShortAndOpeningOrPlaying = parameterTypes.indexOfFirst { it == "Z" } + 1
// On some app targets the method has too many registers pushing the parameters past v15.
// If needed, move the parameters to 4-bit registers so they can be passed to extension.
playerResponseMethodCopyRegisters = implementation!!.registerCount -