feat: Update YouTube & YouTube Music patches (#6571)

This commit is a squash of multiple commits, authored by the individuals referenced below. To see the exact commits by each author, see the unsquashed tree at https://github.com/ReVanced/revanced-patches/pull/6571 or with commit 03940665d27a42ed08992757dfe4534bd8243356.

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: hoodles <207470673+hoo-dles@users.noreply.github.com>
Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com>
Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
Co-authored-by: OxrxL <108184954+oxrxl@users.noreply.github.com>
This commit is contained in:
oSumAtrIX 2026-02-07 23:45:08 +01:00
parent db5e0fe587
commit 88d33b847d
No known key found for this signature in database
GPG key ID: A9B3094ACDB604B4
300 changed files with 8226 additions and 3643 deletions

View file

@ -485,6 +485,10 @@ public final class app/revanced/patches/music/misc/androidauto/UnlockAndroidAuto
public static final fun getUnlockAndroidAutoMediaBrowserPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/music/misc/audio/ForceOriginalAudioPatchKt {
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
public static final fun getRemoveBackgroundPlaybackRestrictionsPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -539,10 +543,6 @@ public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPat
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatchKt {
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/Patch;
public static final fun is_7_16_or_greater ()Z
@ -551,6 +551,8 @@ public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
public static final fun is_8_10_or_greater ()Z
public static final fun is_8_11_or_greater ()Z
public static final fun is_8_15_or_greater ()Z
public static final fun is_8_40_or_greater ()Z
public static final fun is_8_41_or_greater ()Z
}
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
@ -1479,15 +1481,11 @@ public final class app/revanced/patches/youtube/ad/general/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/ad/getpremium/HideGetPremiumPatchKt {
public static final fun getHideGetPremiumPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/ad/video/VideoAdsPatchKt {
public static final fun getVideoAdsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlPatchKt {
public final class app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatchKt {
public static final fun getCopyVideoURLPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1495,6 +1493,10 @@ public final class app/revanced/patches/youtube/interaction/dialog/RemoveViewerD
public static final fun getRemoveViewerDiscretionDialogPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatchKt {
public static final fun getAddMoreDoubleTapToSeekLengthOptionsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatchKt {
public static final fun getDisableDoubleTapActionsPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1503,18 +1505,22 @@ public final class app/revanced/patches/youtube/interaction/downloads/DownloadsP
public static final fun getDownloadsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatchKt {
public static final fun getDisableHapticFeedbackPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/DisablePreciseSeekingGesturePatchKt {
public static final fun getDisablePreciseSeekingGesturePatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/EnableSeekbarTappingPatchKt {
public static final fun getEnableSeekbarTappingPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/EnableSlideToSeekPatchKt {
public static final fun getEnableSlideToSeekPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/EnableTapToSeekPatchKt {
public static final fun getEnableTapToSeekPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/HideSeekbarPatchKt {
public static final fun getHideSeekbarPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1523,10 +1529,6 @@ public final class app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch
public static final fun getSeekbarPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/seekbar/SeekbarThumbnailsPatchKt {
public static final fun getSeekbarThumbnailsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatchKt {
public static final fun getSwipeControlsPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1543,12 +1545,12 @@ public final class app/revanced/patches/youtube/layout/branding/header/ChangeHea
public static final fun getChangeHeaderPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatchKt {
public final class app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatchKt {
public static final fun getHideVideoActionButtonsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/buttons/navigation/NavigationButtonsPatchKt {
public static final fun getNavigationButtonsPatch ()Lapp/revanced/patcher/patch/Patch;
public final class app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatchKt {
public static final fun getNavigationBarPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatchKt {
@ -1559,11 +1561,15 @@ public final class app/revanced/patches/youtube/layout/formfactor/ChangeFormFact
public static final fun getChangeFormFactorPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatchKt {
public static final fun getHideAutoplayPreviewPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatchKt {
public static final fun getHideEndScreenCardsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/endscreensuggestion/HideEndScreenSuggestedVideoPatchKt {
public final class app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatchKt {
public static final fun getHideEndScreenSuggestedVideoPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1579,10 +1585,14 @@ public final class app/revanced/patches/youtube/layout/hide/infocards/HideInfoCa
public static final fun getHideInfoCardsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatchKt {
public final class app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatchKt {
public static final fun getHidePlayerFlyoutMenuItemsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatchKt {
public static final fun getDisablePlayerPopupPanelsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatchKt {
public static final fun getHideRelatedVideoOverlayPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1595,7 +1605,7 @@ public final class app/revanced/patches/youtube/layout/hide/shorts/HideShortsCom
public static final fun getHideShortsComponentsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTvPatchPopupKt {
public final class app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatchKt {
public static final fun getDisableSignInToTVPopupPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1607,10 +1617,6 @@ public final class app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatc
public static final fun getMiniplayerPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatchKt {
public static final fun getDisablePlayerPopupPanelsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatchKt {
public static final fun getExitFullscreenPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1637,10 +1643,6 @@ public final class app/revanced/patches/youtube/layout/returnyoutubedislike/Vote
public static fun values ()[Lapp/revanced/patches/youtube/layout/returnyoutubedislike/Vote;
}
public final class app/revanced/patches/youtube/layout/searchbar/WideSearchbarPatchKt {
public static final fun getWideSearchBarPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatchKt {
public static final fun getSeekbarColorPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1681,6 +1683,10 @@ public final class app/revanced/patches/youtube/layout/thumbnails/BypassImageReg
public static final fun getBypassImageRegionRestrictionsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/layout/toolbar/ToolbarHookPatchKt {
public static final fun getToolbarHookPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/announcements/AnnouncementsPatchKt {
public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1693,6 +1699,25 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou
public static final fun getRemoveBackgroundPlaybackRestrictionsPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatchKt {
public static final fun addClientVersionHook (Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;Ljava/lang/String;)V
public static final fun addOSNameHook (Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;Ljava/lang/String;)V
public static final fun getHookClientContextPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/contexthook/Endpoint : java/lang/Enum {
public static final field BROWSE Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
public static final field GUIDE Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
public static final field REEL Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
public static final field SEARCH Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getGetEndpointMethods ()[Lkotlin/jvm/functions/Function1;
public final fun getInstructions ()Ljava/lang/String;
public final fun setInstructions (Ljava/lang/String;)V
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
public static fun values ()[Lapp/revanced/patches/youtube/misc/contexthook/Endpoint;
}
public final class app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatchKt {
public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1705,6 +1730,15 @@ public final class app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomain
public static final fun getCheckWatchHistoryDomainNameResolutionPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/engagement/EngagementPanelHookPatchKt {
public static final fun addEngagementPanelIdHook (Ljava/lang/String;)V
public static final fun getEngagementPanelHookPatch ()Lapp/revanced/patcher/patch/Patch;
public static final fun getPanelControllerMethod ()Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;
public static final fun getPanelIdIndex ()I
public static final fun getPanelIdRegister ()I
public static final fun getPanelIdSmaliInstruction ()Ljava/lang/String;
}
public final class app/revanced/patches/youtube/misc/extension/SharedExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/Patch;
}
@ -1717,16 +1751,12 @@ public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/hapticfeedback/DisableHapticFeedbackPatchKt {
public static final fun getDisableHapticFeedbackPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHookKt {
public static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V
public static final fun addImageUrlHook (Ljava/lang/String;Z)V
public static synthetic fun addImageUrlHook$default (Ljava/lang/String;ZILjava/lang/Object;)V
public static final fun addImageUrlSuccessCallbackHook (Ljava/lang/String;)V
public static final fun getCronetImageUrlHookPatch ()Lapp/revanced/patcher/patch/Patch;
public static final fun addImageURLErrorCallbackHook (Ljava/lang/String;)V
public static final fun addImageURLHook (Ljava/lang/String;Z)V
public static synthetic fun addImageURLHook$default (Ljava/lang/String;ZILjava/lang/Object;)V
public static final fun addImageURLSuccessCallbackHook (Ljava/lang/String;)V
public static final fun getCronetImageURLHookPatch ()Lapp/revanced/patcher/patch/Patch;
}
public final class app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatchKt {
@ -1799,14 +1829,23 @@ public final class app/revanced/patches/youtube/misc/playservice/VersionCheckPat
public static final fun is_20_22_or_greater ()Z
public static final fun is_20_26_or_greater ()Z
public static final fun is_20_28_or_greater ()Z
public static final fun is_20_29_or_greater ()Z
public static final fun is_20_30_or_greater ()Z
public static final fun is_20_31_or_greater ()Z
public static final fun is_20_34_or_greater ()Z
public static final fun is_20_37_or_greater ()Z
public static final fun is_20_39_or_greater ()Z
public static final fun is_20_40_or_greater ()Z
public static final fun is_20_41_or_greater ()Z
public static final fun is_20_45_or_greater ()Z
public static final fun is_20_46_or_greater ()Z
public static final fun is_20_49_or_greater ()Z
public static final fun is_21_02_or_greater ()Z
public static final fun is_21_03_or_greater ()Z
public static final fun is_21_05_or_greater ()Z
public static final fun is_21_06_or_greater ()Z
public static final fun is_21_07_or_greater ()Z
public static final fun is_21_08_or_greater ()Z
}
public final class app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatchKt {
@ -1913,17 +1952,22 @@ public final class app/revanced/patches/youtube/video/videoid/VideoIdPatchKt {
}
public final class app/revanced/util/BytecodeUtilsKt {
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;ILjava/lang/String;)V
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/extensions/ExternalLabel;)V
public static final fun cloneMutable (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;ILjava/util/List;Ljava/lang/String;I)Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;
public static synthetic fun cloneMutable$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;ILjava/util/List;Ljava/lang/String;IILjava/lang/Object;)Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;
public static final fun cloneMutableAndPreserveParameters (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;
public static final fun cloneMutableAndPreserveParameters (Lcom/android/tools/smali/dexlib2/iface/Method;Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableClassDef;)Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableMethod;
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
public static final fun findFreeRegister (Lcom/android/tools/smali/dexlib2/iface/Method;I[I)I
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun forEachInstructionAsSequence (Lapp/revanced/patcher/patch/BytecodePatchContext;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)V
public static final fun getNumberOfParameterRegisters (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun getNumberOfParameterRegistersLogical (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun getP0Register (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;)I
public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)I
public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
@ -1983,6 +2027,24 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun traverseClassHierarchy (Lapp/revanced/patcher/patch/BytecodePatchContext;Lapp/revanced/com/android/tools/smali/dexlib2/mutable/MutableClassDef;Lkotlin/jvm/functions/Function1;)V
}
public final class app/revanced/util/FreeRegisterProvider {
public final fun availableCount ()I
public final fun getAllocatedFreeRegisters ()Ljava/util/List;
public final fun getFreeRegister ()I
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getUsedAndUnAvailableRegisters ()Ljava/util/List;
public final fun hasFreeRegisters ()Z
}
public final class app/revanced/util/FreeRegisterProviderKt {
public static final fun findFreeRegister (Lcom/android/tools/smali/dexlib2/iface/Method;ILjava/util/List;)I
public static final fun findFreeRegister (Lcom/android/tools/smali/dexlib2/iface/Method;I[I)I
public static final fun getFreeRegisterProvider (Lcom/android/tools/smali/dexlib2/iface/Method;IILjava/util/List;)Lapp/revanced/util/FreeRegisterProvider;
public static final fun getFreeRegisterProvider (Lcom/android/tools/smali/dexlib2/iface/Method;II[I)Lapp/revanced/util/FreeRegisterProvider;
public static final fun getRegistersUsed (Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)Ljava/util/List;
public static final fun getWriteRegister (Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)Ljava/lang/Integer;
}
public final class app/revanced/util/ResourceGroup {
public fun <init> (Ljava/lang/String;[Ljava/lang/String;)V
public final fun getResourceDirectoryName ()Ljava/lang/String;

View file

@ -35,23 +35,10 @@ interface IMethodCall {
instruction: Instruction35c,
instructionIndex: Int,
) {
val registers = arrayOf(
instruction.registerC,
instruction.registerD,
instruction.registerE,
instruction.registerF,
instruction.registerG,
)
val argsNum = methodParams.size + 1 // + 1 for instance of definedClassName
if (argsNum > registers.size) {
// should never happen, but just to be sure (also for the future) a safety check
throw RuntimeException(
"Not enough registers for $definedClassName#$methodName: " +
"Required $argsNum registers, but only got ${registers.size}.",
)
val args = with(instruction) {
arrayOf(registerC, registerD, registerE, registerF, registerG)
.take(registerCount).joinToString(", ") { "v$it" }
}
val args = registers.take(argsNum).joinToString(separator = ", ") { reg -> "v$reg" }
val replacementMethod =
"$methodName(${definedClassName}${methodParams.joinToString(separator = "")})$returnType"

View file

@ -9,7 +9,8 @@ import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideVideoAdsPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/HideVideoAdsPatch;"
@Suppress("unused")
val hideMusicVideoAdsPatch = bytecodePatch(
@ -26,6 +27,8 @@ val hideMusicVideoAdsPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)

View file

@ -19,6 +19,8 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)

View file

@ -12,7 +12,8 @@ import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.findFreeRegister
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/PermanentRepeatPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/PermanentRepeatPatch;"
@Suppress("unused")
val permanentRepeatPatch = bytecodePatch(
@ -29,6 +30,8 @@ val permanentRepeatPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)

View file

@ -29,15 +29,15 @@ private val disableSplashAnimationPatch = bytecodePatch {
// causing the original YT Music logo to momentarily flash on screen as the animation starts.
//
// Could replace the lottie animation file with our own custom animation (app_launch.json),
// but the animation is not always the same size as the launch screen and it's still
// barely shown. Instead turn off the animation entirely (app will also launch a little faster).
// but the animation is not always the same size as the launch screen, and it's still
// barely shown. Instead, turn off the animation entirely (app will also launch a little faster).
cairoSplashAnimationConfigMethod.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
ResourceType.LAYOUT["main_activity_launch_animation"],
)
val checkCastIndex = indexOfFirstInstructionOrThrow(literalIndex) {
opcode == Opcode.CHECK_CAST &&
getReference<TypeReference>()?.type == "Lcom/airbnb/lottie/LottieAnimationView;"
getReference<TypeReference>()?.type == "Lcom/airbnb/lottie/LottieAnimationView;"
}
val register = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
@ -73,6 +73,8 @@ val customBrandingPatch = baseCustomBrandingPatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
},

View file

@ -7,8 +7,8 @@ import app.revanced.patches.shared.misc.mapping.ResourceType
internal val BytecodePatchContext.cairoSplashAnimationConfigMethod by gettingFirstMethodDeclaratively {
name("onCreate")
returnType("V")
definingClass(YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE)
returnType("V")
parameterTypes("Landroid/os/Bundle;")
instructions(ResourceType.LAYOUT("main_activity_launch_animation"))
}

View file

@ -27,9 +27,7 @@ internal val BytecodePatchContext.historyMenuItemMethodMatch by composingFirstMe
Opcode.RETURN_VOID,
)
literal { historyMenuItem }
custom {
immutableClassDef.methods.count() == 5
}
custom { immutableClassDef.methods.count() == 5 || immutableClassDef.methods.count() == 4 }
}
internal val BytecodePatchContext.historyMenuItemOfflineTabMethodMatch by composingFirstMethod {

View file

@ -31,7 +31,8 @@ internal var searchButton = -1L
internal var topBarMenuItemImageView = -1L
private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideButtonsPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/HideButtonsPatch;"
@Suppress("unused")
val hideButtonsPatch = bytecodePatch(
@ -49,6 +50,8 @@ val hideButtonsPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
@ -91,7 +94,11 @@ val hideButtonsPatch = bytecodePatch(
arrayOf(
Triple(playerOverlayChipMethod, playerOverlayChip, "hideCastButton"),
Triple(searchActionViewMethod, searchButton, "hideSearchButton"),
Triple(topBarMenuItemImageViewMethod, topBarMenuItemImageView, "hideNotificationButton"),
Triple(
topBarMenuItemImageViewMethod,
topBarMenuItemImageView,
"hideNotificationButton"
),
).forEach { (method, resourceIdLiteral, methodName) ->
method.apply {
val resourceIndex = indexOfFirstLiteralInstructionOrThrow(resourceIdLiteral)
@ -104,7 +111,7 @@ val hideButtonsPatch = bytecodePatch(
addInstruction(
targetIndex + 1,
"invoke-static { v$targetRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V",
"$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V",
)
}
}

View file

@ -15,7 +15,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
internal var chipCloud = -1L
private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCategoryBarPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/HideCategoryBarPatch;"
@Suppress("unused")
val hideCategoryBarPatch = bytecodePatch(
@ -32,6 +33,8 @@ val hideCategoryBarPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
@ -46,7 +49,8 @@ val hideCategoryBarPatch = bytecodePatch(
chipCloudMethodMatch.let {
val targetIndex = it[-1]
val targetRegister = it.method.getInstruction<OneRegisterInstruction>(targetIndex).registerA
val targetRegister =
it.method.getInstruction<OneRegisterInstruction>(targetIndex).registerA
it.method.addInstruction(
targetIndex + 1,

View file

@ -8,5 +8,12 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
lithoFilterPatch = lithoFilterPatch,
settingsPatch = settingsPatch,
filterClasses = setOf("Lapp/revanced/extension/shared/patches/litho/CustomFilter;"),
compatibleWithPackages = arrayOf("com.google.android.apps.youtube.music" to setOf("7.29.52", "8.10.52")),
compatibleWithPackages = arrayOf(
"com.google.android.apps.youtube.music" to setOf(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54"
)
),
)

View file

@ -22,7 +22,8 @@ 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
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/ChangeMiniplayerColorPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/ChangeMiniplayerColorPatch;"
@Suppress("unused")
val changeMiniplayerColorPatch = bytecodePatch(
@ -40,6 +41,8 @@ val changeMiniplayerColorPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
@ -71,10 +74,11 @@ val changeMiniplayerColorPatch = bytecodePatch(
miniPlayerConstructorMethodMatch.immutableMethod.indexOfFirstInstructionReversedOrThrow {
getReference<MethodReference>()?.name == "getColor"
}
val iPutIndex = miniPlayerConstructorMethodMatch.immutableMethod.indexOfFirstInstructionOrThrow(
colorGreyIndex,
Opcode.IPUT,
)
val iPutIndex =
miniPlayerConstructorMethodMatch.immutableMethod.indexOfFirstInstructionOrThrow(
colorGreyIndex,
Opcode.IPUT,
)
val colorMathPlayerIPutReference = miniPlayerConstructorMethodMatch.immutableMethod
.getInstruction<ReferenceInstruction>(iPutIndex).reference

View file

@ -7,12 +7,11 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.miniPlayerConstructorMethodMatch by composingFirstMethod {
returnType("V")
instructions(
ResourceType.ID("mpp_player_bottom_sheet"),
"sharedToggleMenuItemMutations"(),
)
internal val BytecodePatchContext.miniPlayerConstructorMethodMatch by composingFirstMethod(
"sharedToggleMenuItemMutations"
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(ResourceType.ID("music_playback_controls"))
}
/**

View file

@ -2,34 +2,45 @@ package app.revanced.patches.music.layout.navigationbar
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val BytecodePatchContext.tabLayoutTextMethodMatch by composingFirstMethod("FEmusic_search") {
internal val BytecodePatchContext.tabLayoutTextMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("L")
opcodes(
Opcode.IGET,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT,
)
custom {
containsLiteralInstruction(text1) &&
indexOfGetVisibilityInstruction(this) >= 0
}
}
instructions(
anyOf(
"FEmusic_search"(), // 8.49 and lower.
"FEsearch"() // 8.50+
),
// Hide navigation label.
ResourceType.ID("text1"),
afterAtMost(
5,
method { toString() == "Landroid/view/View;->findViewById(I)Landroid/view/View;" }
),
afterAtMost(
5,
allOf(Opcode.CHECK_CAST(), type("Landroid/widget/TextView;"))
),
// Set navigation enum.
anyOf(
Opcode.SGET_OBJECT(),
Opcode.IGET_OBJECT()
),
afterAtMost(5, allOf(Opcode.IGET(), field { type == "I" })),
internal fun indexOfGetVisibilityInstruction(method: Method) = method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "getVisibility"
afterAtMost(
5,
allOf(
Opcode.INVOKE_STATIC(),
method { returnType == "L" && parameterTypes.size == 1 && parameterTypes.first() == "I" })
),
after(Opcode.MOVE_RESULT_OBJECT()),
// Hide navigation buttons.
method("getVisibility")
)
}

View file

@ -2,7 +2,6 @@ package app.revanced.patches.music.layout.navigationbar
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
@ -14,9 +13,6 @@ import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
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
@ -24,7 +20,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
internal var text1 = -1L
private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/NavigationBarPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/NavigationBarPatch;"
@Suppress("unused")
val navigationBarPatch = bytecodePatch(
@ -58,6 +55,8 @@ val navigationBarPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
@ -83,39 +82,44 @@ val navigationBarPatch = bytecodePatch(
),
)
tabLayoutTextMethodMatch.method.apply {
// Hide navigation labels.
val constIndex = indexOfFirstLiteralInstructionOrThrow(text1)
val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST)
val targetParameter = getInstruction<ReferenceInstruction>(targetIndex).reference
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
tabLayoutTextMethodMatch.let {
it.method.apply {
// Modify in reverse order to preserve match indices.
if (!targetParameter.toString().endsWith("Landroid/widget/TextView;")) {
throw PatchException("Method signature parameter did not match: $targetParameter")
// Hide navigation buttons.
val pivotTabIndex = it[-1]
val pivotTabRegister =
getInstruction<FiveRegisterInstruction>(pivotTabIndex).registerC
addInstruction(
pivotTabIndex,
"invoke-static { v$pivotTabRegister }, " +
"${EXTENSION_CLASS_DESCRIPTOR}->hideNavigationButton(Landroid/view/View;)V"
)
// Set navigation enum and hide navigation buttons.
val enumIndex = it[7]
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
addInstruction(
enumIndex + 1,
"invoke-static { v$enumRegister }, " +
"${EXTENSION_CLASS_DESCRIPTOR}->setLastAppNavigationEnum(Ljava/lang/Enum;)V"
)
// Hide navigation labels.
val labelIndex = it[3]
val targetParameter = getInstruction<ReferenceInstruction>(labelIndex).reference
val targetRegister = getInstruction<OneRegisterInstruction>(labelIndex).registerA
addInstruction(
labelIndex + 1,
"invoke-static { v$targetRegister }, " +
"${EXTENSION_CLASS_DESCRIPTOR}->hideNavigationLabel(Landroid/widget/TextView;)V"
)
}
addInstruction(
targetIndex + 1,
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationLabel(Landroid/widget/TextView;)V",
)
// Set navigation enum and hide navigation buttons.
val enumIndex = tabLayoutTextMethodMatch[0] + 3
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2
val pivotTabIndex = indexOfGetVisibilityInstruction(this)
val pivotTabRegister = getInstruction<FiveRegisterInstruction>(pivotTabIndex).registerC
addInstruction(
pivotTabIndex,
"invoke-static { v$pivotTabRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationButton(Landroid/view/View;)V",
)
addInstruction(
insertEnumIndex,
"invoke-static { v$enumRegister }, $EXTENSION_CLASS_DESCRIPTOR->setLastAppNavigationEnum(Ljava/lang/Enum;)V",
)
}
}
}

View file

@ -13,7 +13,8 @@ import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideGetPremiumPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/HideGetPremiumPatch;"
@Suppress("unused")
val hideGetMusicPremiumPatch = bytecodePatch(
@ -30,6 +31,8 @@ val hideGetMusicPremiumPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
@ -56,7 +59,7 @@ val hideGetMusicPremiumPatch = bytecodePatch(
addInstruction(
insertIndex + 1,
"invoke-virtual {v$getPremiumViewRegister, v$visibilityRegister}, " +
"Landroid/view/View;->setVisibility(I)V",
"Landroid/view/View;->setVisibility(I)V",
)
}
}

View file

@ -17,19 +17,23 @@ val themePatch = baseThemePatch(
dependsOn(
sharedExtensionPatch,
baseThemeResourcePatch(
darkColorNames = THEME_DEFAULT_DARK_COLOR_NAMES + setOf(
"yt_black_pure",
"yt_black_pure_opacity80",
"ytm_color_grey_12",
"material_grey_800"
)
getDarkColorNames = {
THEME_DEFAULT_DARK_COLOR_NAMES + setOf(
"yt_black_pure",
"yt_black_pure_opacity80",
"ytm_color_grey_12",
"material_grey_800"
)
}
)
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
"8.10.52",
"8.37.56",
"8.40.54"
)
)
},

View file

@ -18,19 +18,25 @@ val unlockAndroidAutoMediaBrowserPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
apply {
checkCertificateMethod.returnEarly(true)
searchMediaItemsConstructorMethod.immutableClassDef.getSearchMediaItemsExecuteMethod().apply {
val targetIndex = instructions.indexOfFirst {
it.opcode == Opcode.IGET_OBJECT && it.fieldReference?.type == "Ljava/lang/String;"
}
searchMediaItemsConstructorMethod.immutableClassDef.getSearchMediaItemsExecuteMethod()
.apply {
val targetIndex = instructions.indexOfFirst {
it.opcode == Opcode.IGET_OBJECT && it.fieldReference?.type == "Ljava/lang/String;"
}
val register = instructions[targetIndex].registersUsed.first()
replaceInstruction(targetIndex, "const-string v$register, \"com.google.android.apps.youtube.music\"")
}
val register = instructions[targetIndex].registersUsed.first()
replaceInstruction(
targetIndex,
"const-string v$register, \"com.google.android.apps.youtube.music\""
)
}
}
}

View file

@ -1,4 +1,4 @@
package app.revanced.patches.music.misc.tracks
package app.revanced.patches.music.misc.audio
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
@ -20,7 +20,9 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch(
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
"8.10.52",
"8.37.56",
"8.40.54"
)
)
},

View file

@ -19,6 +19,8 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)

View file

@ -3,19 +3,31 @@ package app.revanced.patches.music.misc.debugging
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.music.playservice.is_8_40_or_greater
import app.revanced.patches.music.playservice.is_8_41_or_greater
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
@Suppress("unused")
val enableDebuggingPatch = enableDebuggingPatch(
sharedExtensionPatch = sharedExtensionPatch,
settingsPatch = settingsPatch,
compatibleWithPackages = arrayOf(
"com.google.android.apps.youtube.music" to setOf(
"7.29.52",
"8.10.52"
block = {
dependsOn(
sharedExtensionPatch,
settingsPatch,
)
),
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
)
)
},
// String feature flag does not appear to be present with YT Music.
hookStringFeatureFlag = false,
hookStringFeatureFlag = { false },
// 8.40 has changes not worth supporting.
hookLongFeatureFlag = { !is_8_40_or_greater || is_8_41_or_greater },
hookDoubleFeatureFlag = { !is_8_40_or_greater || is_8_41_or_greater },
preferenceScreen = PreferenceScreen.MISC,
)

View file

@ -13,7 +13,9 @@ val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameReso
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
"8.10.52",
"8.37.56",
"8.40.54"
)
)
},

View file

@ -32,6 +32,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
MUSIC_PACKAGE_NAME(
"7.29.52",
"8.10.52",
"8.37.56",
"8.40.54",
),
)
}

View file

@ -16,7 +16,9 @@ val sanitizeSharingLinksPatch = sanitizeSharingLinksPatch(
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
"8.10.52",
"8.37.56",
"8.40.54"
)
)
},

View file

@ -8,14 +8,18 @@ import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
import app.revanced.patches.music.playservice.is_8_40_or_greater
import app.revanced.patches.music.playservice.versionCheckPatch
import app.revanced.patches.shared.boldIconsFeatureFlagMethodMatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.*
import app.revanced.patches.shared.misc.settings.settingsPatch
import app.revanced.patches.youtube.misc.settings.modifyActivityForSettingsInjection
import app.revanced.util.copyXmlNode
import app.revanced.util.inputStreamFromBundledResource
import app.revanced.util.insertLiteralOverride
private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
private const val MUSIC_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/settings/MusicActivityHook;"
private val preferences = mutableSetOf<BasePreference>()
@ -72,6 +76,7 @@ val settingsPatch = bytecodePatch(
sharedExtensionPatch,
settingsResourcePatch,
addResourcesPatch,
versionCheckPatch
)
apply {
@ -90,6 +95,12 @@ val settingsPatch = bytecodePatch(
SwitchPreference("revanced_settings_search_history")
)
if (is_8_40_or_greater) {
PreferenceScreen.GENERAL.addPreferences(
SwitchPreference("revanced_settings_disable_bold_icons")
)
}
PreferenceScreen.MISC.addPreferences(
TextPreference(
key = null,
@ -103,9 +114,18 @@ val settingsPatch = bytecodePatch(
modifyActivityForSettingsInjection(
googleApiActivityMethod.classDef,
googleApiActivityMethod,
GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR,
MUSIC_ACTIVITY_HOOK_CLASS_DESCRIPTOR,
true
)
if (is_8_40_or_greater) {
boldIconsFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$MUSIC_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useBoldIcons(Z)Z"
)
}
}
}
afterDependents {

View file

@ -31,7 +31,9 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
"8.10.52",
"8.37.56",
"8.40.54"
)
)
},

View file

@ -20,8 +20,11 @@ var is_8_11_or_greater: Boolean by Delegates.notNull()
private set
var is_8_15_or_greater: Boolean by Delegates.notNull()
private set
var is_8_40_or_greater: Boolean by Delegates.notNull()
private set
var is_8_41_or_greater: Boolean by Delegates.notNull()
private set
@Suppress("unused")
val versionCheckPatch = resourcePatch(
description = "Uses the Play Store service version to find the major/minor version of the YouTube Music target app."
) {
@ -38,5 +41,7 @@ val versionCheckPatch = resourcePatch(
is_8_10_or_greater = 251099000 <= playStoreServicesVersion
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
is_8_40_or_greater = 254080000 <= playStoreServicesVersion
is_8_41_or_greater = 254180000 <= playStoreServicesVersion
}
}

View file

@ -1,7 +1,14 @@
package app.revanced.patches.shared
import app.revanced.patcher.accessFlags
import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.castContextFetchMethod by gettingFirstMethodDeclaratively(
"Error fetching CastContext."
@ -11,3 +18,14 @@ internal val BytecodePatchContext.primeMethod by gettingFirstMethodDeclaratively
"com.android.vending",
"com.google.android.GoogleCamera"
)
// Flag is present in YouTube 20.23, but bold icons are missing and forcing them crashes the app.
// 20.31 is the first target with all the bold icons present.
internal val BytecodePatchContext.boldIconsFeatureFlagMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
parameterTypes()
instructions(
45685201L(),
)
}

View file

@ -1,8 +1,10 @@
package app.revanced.patches.shared.layout.branding
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.rawResourcePatch
import app.revanced.util.inputStreamFromBundledResource
import java.nio.file.Files
import java.util.logging.Logger
/**
* Copies a branding license text file to the target apk.
@ -20,6 +22,8 @@ internal val addBrandLicensePatch = rawResourcePatch {
val targetFile = get(brandingLicenseFileName, false).toPath()
Files.copy(inputFileStream, targetFile)
if (Files.exists(targetFile)) Logger.getLogger(this::class.java.name)
.warning("Already patched by ReVanced")
else Files.copy(inputFileStream, targetFile)
}
}

View file

@ -3,6 +3,7 @@ package app.revanced.patches.shared.layout.branding
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.firstImmutableClassDef
import app.revanced.patcher.patch.*
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.all.misc.resources.addResources
@ -52,9 +53,11 @@ private val USER_CUSTOM_ADAPTIVE_FILE_NAMES = arrayOf(
private const val USER_CUSTOM_MONOCHROME_FILE_NAME =
"$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml"
private const val USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME = "${NOTIFICATION_ICON_NAME}_$CUSTOM_USER_ICON_STYLE_NAME.xml"
private const val USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME =
"${NOTIFICATION_ICON_NAME}_$CUSTOM_USER_ICON_STYLE_NAME.xml"
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/CustomBrandingPatch;"
internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/shared/patches/CustomBrandingPatch;"
/**
* Shared custom branding patch for YouTube and YT Music.
@ -75,7 +78,7 @@ internal fun baseCustomBrandingPatch(
) = resourcePatch(
name = "Custom branding",
description = "Adds options to change the app icon and app name. " +
"Branding cannot be changed for mounted (root) installations.",
"Branding cannot be changed for mounted (root) installations.",
) {
val customName by stringOption(
name = "App name",
@ -116,6 +119,8 @@ internal fun baseCustomBrandingPatch(
)
numberOfPresetAppNamesExtensionMethod.returnEarly(numberOfPresetAppNames)
userProvidedCustomNameExtensionMethod.returnEarly(customName != null)
userProvidedCustomIconExtensionMethod.returnEarly(customIcon != null)
notificationMethod.apply {
val getBuilderIndex = if (isYouTubeMusic) {
@ -128,7 +133,7 @@ internal fun baseCustomBrandingPatch(
val builderCastIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<TypeReference>()
opcode == Opcode.CHECK_CAST &&
reference?.type == "Landroid/app/Notification\$Builder;"
reference?.type == "Landroid/app/Notification\$Builder;"
}
indexOfFirstInstructionReversedOrThrow(builderCastIndex) {
getReference<FieldReference>()?.type == "Ljava/lang/Object;"
@ -173,11 +178,14 @@ internal fun baseCustomBrandingPatch(
}
apply {
val useCustomName = customName != null
val useCustomIcon = customIcon != null
addResources("shared", "layout.branding.baseCustomBrandingPatch")
addResources(addResourcePatchName, "layout.branding.customBrandingPatch")
preferenceScreen.addPreferences(
if (customName != null) {
if (useCustomName) {
ListPreference(
key = "revanced_custom_branding_name",
entriesKey = "revanced_custom_branding_name_custom_entries",
@ -186,7 +194,7 @@ internal fun baseCustomBrandingPatch(
} else {
ListPreference("revanced_custom_branding_name")
},
if (customIcon != null) {
if (useCustomIcon) {
ListPreference(
key = "revanced_custom_branding_icon",
entriesKey = "revanced_custom_branding_icon_custom_entries",
@ -197,9 +205,6 @@ internal fun baseCustomBrandingPatch(
},
)
val useCustomName = customName != null
val useCustomIcon = customIcon != null
iconStyleNames.forEach { style ->
copyResources(
"custom-branding",
@ -307,7 +312,7 @@ internal fun baseCustomBrandingPatch(
activityAliasNameWithIntents,
).childNodes
// The YT application name can appear in some places along side the system
// The YT application name can appear in some places alongside the system
// YouTube app, such as the settings app list and in the "open with" file picker.
// Because the YouTube app cannot be completely uninstalled and only disabled,
// use a custom name for this situation to disambiguate which app is which.
@ -316,6 +321,9 @@ internal fun baseCustomBrandingPatch(
"@string/revanced_custom_branding_name_entry_2",
)
val enabledNameIndex = if (useCustomName) numberOfPresetAppNames else 1 // 1 indexing.
val enabledIconIndex = if (useCustomIcon) iconStyleNames.size else 0 // 0 indexing.
for (appNameIndex in 1..numberOfPresetAppNames) {
fun aliasName(name: String): String = ".revanced_" + name + '_' + appNameIndex
@ -334,14 +342,14 @@ internal fun baseCustomBrandingPatch(
)
// Bundled icons.
iconStyleNames.forEach { style ->
iconStyleNames.forEachIndexed { iconIndex, style ->
application.appendChild(
createAlias(
aliasName = aliasName(style),
iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + style,
appNameIndex = appNameIndex,
useCustomName = useCustomNameLabel,
enabled = false,
enabled = (appNameIndex == enabledNameIndex && iconIndex == enabledIconIndex),
intentFilters,
),
)
@ -351,7 +359,7 @@ internal fun baseCustomBrandingPatch(
//
// Must add all aliases even if the user did not provide a custom icon of their own.
// This is because if the user installs with an option, then repatches without the option,
// the alias must still exist because if it was previously enabled and then it's removed
// the alias must still exist because if it was previously enabled, and then it's removed
// the app will become broken and cannot launch. Even if the app data is cleared
// it still cannot be launched and the only fix is to uninstall the app.
// To prevent this, always include all aliases and use dummy data if needed.
@ -361,7 +369,7 @@ internal fun baseCustomBrandingPatch(
iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + CUSTOM_USER_ICON_STYLE_NAME,
appNameIndex = appNameIndex,
useCustomName = useCustomNameLabel,
enabled = false,
enabled = appNameIndex == enabledNameIndex && useCustomIcon,
intentFilters,
),
)
@ -413,7 +421,7 @@ internal fun baseCustomBrandingPatch(
if (customFiles.isNotEmpty() && customFiles.size != USER_CUSTOM_ADAPTIVE_FILE_NAMES.size) {
throw PatchException(
"Must include all required icon files " +
"but only found " + customFiles.map { it.name },
"but only found " + customFiles.map { it.name },
)
}
@ -444,8 +452,8 @@ internal fun baseCustomBrandingPatch(
if (!copiedFiles) {
throw PatchException(
"Expected to find directories and files: " +
USER_CUSTOM_ADAPTIVE_FILE_NAMES.contentToString() +
"\nBut none were found in the provided option file path: " + iconPathFile.absolutePath,
USER_CUSTOM_ADAPTIVE_FILE_NAMES.contentToString() +
"\nBut none were found in the provided option file path: " + iconPathFile.absolutePath,
)
}
}

View file

@ -5,13 +5,30 @@ import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
internal val BytecodePatchContext.numberOfPresetAppNamesExtensionMethod by gettingFirstMethodDeclaratively {
name("numberOfPresetAppNames")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
name("numberOfPresetAppNames")
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("I")
parameterTypes()
}
internal val BytecodePatchContext.userProvidedCustomNameExtensionMethod by gettingFirstMethodDeclaratively {
definingClass(EXTENSION_CLASS_DESCRIPTOR)
name("userProvidedCustomName")
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Z")
parameterTypes()
}
internal val BytecodePatchContext.userProvidedCustomIconExtensionMethod by gettingFirstMethodDeclaratively {
definingClass(EXTENSION_CLASS_DESCRIPTOR)
name("userProvidedCustomIcon")
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Z")
parameterTypes()
}
// A much simpler method exists that can set the small icon (contains string "414843287017"),
// but that has limited usage and this one allows changing any part of the notification.
internal val BytecodePatchContext.notificationMethod by gettingFirstMethodDeclaratively(

View file

@ -1,20 +1,26 @@
package app.revanced.patches.shared.layout.theme
import app.revanced.patcher.patch.*
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.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.util.childElementsSequence
import java.util.*
internal const val THEME_COLOR_OPTION_DESCRIPTION = "Can be a hex color (#RRGGBB) or a color resource reference."
internal const val THEME_COLOR_OPTION_DESCRIPTION =
"Can be a hex color (#RRGGBB) or a color resource reference."
internal val THEME_DEFAULT_DARK_COLOR_NAMES = setOf(
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98",
"yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark",
"material_grey_850"
"yt_black0", "yt_black1", "yt_black2", "yt_black3", "yt_black4",
"yt_black1_opacity95", "yt_black1_opacity98",
"yt_status_bar_background_dark", "material_grey_850",
)
internal val THEME_DEFAULT_LIGHT_COLOR_NAMES = setOf(
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4"
"yt_white1", "yt_white2", "yt_white3", "yt_white4",
"yt_white1_opacity95", "yt_white1_opacity98",
)
/**
@ -94,8 +100,8 @@ internal fun baseThemePatch(
}
internal fun baseThemeResourcePatch(
darkColorNames: Set<String> = THEME_DEFAULT_DARK_COLOR_NAMES,
lightColorNames: Set<String> = THEME_DEFAULT_LIGHT_COLOR_NAMES,
getDarkColorNames: () -> Set<String> = { THEME_DEFAULT_DARK_COLOR_NAMES },
getLightColorNames: () -> Set<String> = { THEME_DEFAULT_LIGHT_COLOR_NAMES },
lightColorReplacement: (() -> String)? = null
) = resourcePatch {
apply {
@ -114,6 +120,9 @@ internal fun baseThemeResourcePatch(
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0)
val darkColorNames = getDarkColorNames()
val lightColorNames = getLightColorNames()
resourcesNode.childElementsSequence().forEach { node ->
val name = node.getAttribute("name")
when {

View file

@ -11,7 +11,7 @@ internal val BytecodePatchContext.lithoOnBoundsChangeMethodMatch by composingFir
returnType("V")
parameterTypes("Landroid/graphics/Rect;")
lateinit var methodDefiningClass: String
var methodDefiningClass = ""
custom {
methodDefiningClass = definingClass
true
@ -23,11 +23,7 @@ internal val BytecodePatchContext.lithoOnBoundsChangeMethodMatch by composingFir
field { type == "Landroid/graphics/Path;" && definingClass == methodDefiningClass },
),
afterAtMost(
5,
method { returnType == "Z" && name == "isStateful" && definingClass == methodDefiningClass },
),
afterAtMost(
5,
10,
allOf(
Opcode.IGET_OBJECT(),
field { type == "Landroid/graphics/Paint;" && definingClass == methodDefiningClass },

View file

@ -14,8 +14,11 @@ import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.cloneMutable
import app.revanced.util.findMethodFromToString
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@ -96,58 +99,50 @@ internal fun forceOriginalAudioPatch(
).toMutable(),
)
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "patch_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
listOf(ImmutableMethodParameter("Z", null, null)),
"Z",
AccessFlags.PRIVATE.value,
null,
null,
MutableMethodImplementation(6),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
if-eqz v0, :call_extension
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v3
return v3
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v2
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v3
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
return v3
""",
)
// Clone the method to add additional registers because the
// isDefaultAudioTrack() has only 1 or 2 registers and 3 are needed.
val clonedMethod = isDefaultAudioTrackMethod.cloneMutable(
additionalRegisters = 4
)
// Replace existing method with cloned with more registers.
methods.apply {
remove(isDefaultAudioTrackMethod)
add(clonedMethod)
}
methods.add(helperMethod)
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultAudioTrackMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA
clonedMethod.apply {
// Free registers are added
val free1 = isDefaultAudioTrackMethod.implementation!!.registerCount + 1
val free2 = free1 + 1
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN)
val originalResultRegister =
getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
index,
clonedMethod.addInstructionsAtControlFlowLabel(
insertIndex,
"""
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
move-result v$register
""",
iget-object v$free1, p0, $type->$helperFieldName:Ljava/lang/Boolean;
if-eqz v$free1, :call_extension
invoke-virtual { v$free1 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v$free1
return v$free1
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v$free1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v$free2
invoke-static { v$originalResultRegister, v$free1, v$free2 }, ${EXTENSION_CLASS_DESCRIPTOR}->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v$free1
invoke-static { v$free1 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v$free2
iput-object v$free2, p0, $type->$helperFieldName:Ljava/lang/Boolean;
return v$free1
"""
)
}
}

View file

@ -1,18 +1,27 @@
package app.revanced.patches.shared.misc.debugging
import app.revanced.patcher.accessFlags
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.method
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.returnType
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.*
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/shared/patches/EnableDebuggingPatch;"
@ -21,21 +30,19 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
* Patch shared with YouTube and YT Music.
*/
internal fun enableDebuggingPatch(
sharedExtensionPatch: Patch,
settingsPatch: Patch,
vararg compatibleWithPackages: Pair<String, Set<String>>,
hookStringFeatureFlag: Boolean,
block: BytecodePatchBuilder.() -> Unit = {},
executeBlock: BytecodePatchContext.() -> Unit = {},
hookStringFeatureFlag: BytecodePatchBuilder.() -> Boolean,
hookLongFeatureFlag: BytecodePatchBuilder.() -> Boolean,
hookDoubleFeatureFlag: BytecodePatchBuilder.() -> Boolean,
preferenceScreen: BasePreferenceScreen.Screen,
additionalDebugPreferences: List<BasePreference> = emptyList()
) = bytecodePatch(
name = "Enable debugging",
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
) {
compatibleWith(packages = compatibleWithPackages)
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
resourcePatch {
apply {
copyResources(
@ -50,36 +57,44 @@ internal fun enableDebuggingPatch(
"revanced_settings_arrow_left_double.xml",
"revanced_settings_arrow_left_one.xml",
"revanced_settings_arrow_right_double.xml",
"revanced_settings_arrow_right_one.xml",
),
"revanced_settings_arrow_right_one.xml"
)
)
}
},
}
)
block()
apply {
executeBlock()
addResources("shared", "misc.debugging.enableDebuggingPatch")
val preferences = setOf(
val preferences = mutableSetOf<BasePreference>(
SwitchPreference("revanced_debug"),
SwitchPreference("revanced_debug_protobuffer"),
)
preferences + additionalDebugPreferences
preferences += listOf(
SwitchPreference("revanced_debug_stacktrace"),
SwitchPreference("revanced_debug_toast_on_error"),
NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard",
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
selectable = true,
selectable = true
),
NonInteractivePreference(
"revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
selectable = true,
selectable = true
),
NonInteractivePreference(
"revanced_debug_feature_flags_manager",
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
selectable = true,
),
selectable = true
)
)
preferenceScreen.addPreferences(
@ -87,67 +102,117 @@ internal fun enableDebuggingPatch(
key = "revanced_debug_screen",
sorting = Sorting.UNSORTED,
preferences = preferences,
),
)
)
// Hook the methods that look up if a feature flag is active.
experimentalFeatureFlagParentMethod.immutableClassDef.getExperimentalBooleanFeatureFlagMethod().apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
val experimentalBooleanFeatureFlagMethodMatch =
experimentalFeatureFlagUtilMethod.immutableClassDef.experimentalBooleanFeatureFlagMethodMatch
experimentalBooleanFeatureFlagMethodMatch.let {
it.method.apply {
// Not enough registers in the method. Clone the method and use the
// original method as an intermediate to call extension code.
// Copy the method.
val helperMethod = cloneMutable(name = "patch_getBooleanFeatureFlag")
// Add the method.
it.classDef.methods.add(helperMethod)
addInstructions(
index,
0,
"""
# Invoke the copied method (helper method).
invoke-static { p0, p1, p2, p3 }, $helperMethod
move-result p0
# Redefine boolean in the extension.
invoke-static { p0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZJ)Z
move-result p0
# Since the copied method (helper method) has already been invoked, it just returns.
return p0
"""
invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z
move-result v$register
""",
)
}
}
experimentalFeatureFlagParentMethod.immutableClassDef.getExperimentalDoubleFeatureFlagMethod().apply {
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
if (hookDoubleFeatureFlag())
// 21.06+ doesn't have enough registers and needs to also clone.
experimentalFeatureFlagUtilMethod.immutableClassDef.getExperimentalDoubleFeatureFlagMethod()
.cloneMutableAndPreserveParameters().apply {
val helperMethod = cloneMutable(name = "patch_getDoubleFeatureFlag")
addInstructions(
insertIndex,
"""
move-result-wide v0 # Also clobbers v1 (p0) since result is wide.
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D
move-result-wide v0
return-wide v0
""",
)
}
classDef.methods.add(helperMethod)
experimentalFeatureFlagParentMethod.immutableClassDef.getExperimentalLongFeatureFlagMethod().apply {
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
addInstructions(
0,
"""
# Invoke the copied method (helper method).
invoke-static/range { p0 .. p4 }, $helperMethod
move-result-wide v0
# Move parameter registers to lower register range to use invoke-static/range.
move-wide v2, p1
move-wide v4, p3
addInstructions(
insertIndex,
"""
move-result-wide v0
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J
move-result-wide v0
return-wide v0
""",
)
}
invoke-static/range { v0 .. v5 }, ${EXTENSION_CLASS_DESCRIPTOR}->isDoubleFeatureFlagEnabled(DJD)D
move-result-wide v0
if (hookStringFeatureFlag) {
experimentalFeatureFlagParentMethod.immutableClassDef.getExperimentalStringFeatureFlagMethod().apply {
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
addInstructions(
insertIndex,
# Since the copied method (helper method) has already been invoked, it just returns.
return-wide v0
"""
move-result-object v0
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
move-result-object v0
return-object v0
""",
)
}
}
)
}
if (hookLongFeatureFlag())
experimentalFeatureFlagUtilMethod.immutableClassDef.getExperimentalLongFeatureFlagMethod()
.cloneMutableAndPreserveParameters().apply {
val helperMethod = cloneMutable(name = "patch_getLongFeatureFlag")
classDef.methods.add(helperMethod)
addInstructions(
0,
"""
# Invoke the copied method (helper method).
invoke-static/range { p0 .. p4 }, $helperMethod
move-result-wide v0
# Move parameter registers to lower register range to use invoke-static/range.
move-wide v2, p1
move-wide v4, p3
invoke-static/range { v0 .. v5 }, ${EXTENSION_CLASS_DESCRIPTOR}->isLongFeatureFlagEnabled(JJJ)J
move-result-wide v0
# Since the copied method (helper method) has already been invoked, it just returns.
return-wide v0
"""
)
}
if (hookStringFeatureFlag())
experimentalFeatureFlagUtilMethod.immutableClassDef.getExperimentalStringFeatureFlagMethod()
.apply {
val helperMethod = cloneMutable(name = "patch_getStringFeatureFlag")
classDef.methods.add(helperMethod)
addInstructions(
0,
"""
invoke-static { p0, p1, p2, p3 }, $helperMethod
move-result-object p0
invoke-static { p0, p1, p2, p3 }, ${EXTENSION_CLASS_DESCRIPTOR}->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
move-result-object p0
return-object p0
"""
)
}
// There exists other experimental accessor methods for byte[]
// and wrappers for obfuscated classes, but currently none of those are hooked.

View file

@ -1,46 +1,55 @@
package app.revanced.patches.shared.misc.debugging
import app.revanced.patcher.ClassDefComposing
import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.accessFlags
import app.revanced.patcher.custom
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.experimentalFeatureFlagParentMethod by gettingFirstImmutableMethodDeclaratively(
internal val BytecodePatchContext.experimentalFeatureFlagUtilMethod by gettingFirstImmutableMethodDeclaratively(
"Unable to parse proto typed experiment flag: "
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("L")
parameterTypes("L", "J", "[B")
custom {
// 'public static' or 'public static final'
AccessFlags.STATIC.isSet(accessFlags)
&& AccessFlags.PUBLIC.isSet(accessFlags)
// "L", "J", "[B" or "L", "J"
&& parameters.let { (it.size == 2 || it.size == 3) && it[1].type == "J" }
}
}
context(_: BytecodePatchContext)
internal fun ClassDef.getExperimentalBooleanFeatureFlagMethod() = firstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
internal val ClassDef.experimentalBooleanFeatureFlagMethodMatch by ClassDefComposing.composingFirstMethod {
returnType("Z")
parameterTypes("L", "J", "Z")
custom {
// 'public static' or 'public static final'
AccessFlags.STATIC.isSet(accessFlags) && AccessFlags.PUBLIC.isSet(accessFlags)
}
}
context(_: BytecodePatchContext)
internal fun ClassDef.getExperimentalDoubleFeatureFlagMethod() = firstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("D")
parameterTypes("J", "D")
parameterTypes("L", "J", "D")
custom { AccessFlags.STATIC.isSet(accessFlags) }
}
context(_: BytecodePatchContext)
internal fun ClassDef.getExperimentalLongFeatureFlagMethod() = firstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("J")
parameterTypes("J", "J")
parameterTypes("L", "J", "J")
custom { AccessFlags.STATIC.isSet(accessFlags) }
}
context(_: BytecodePatchContext)
internal fun ClassDef.getExperimentalStringFeatureFlagMethod() = firstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Ljava/lang/String;")
parameterTypes("J", "Ljava/lang/String;")
parameterTypes("L", "J", "Ljava/lang/String;")
custom { AccessFlags.STATIC.isSet(accessFlags) }
}

View file

@ -3,6 +3,7 @@ package app.revanced.patches.shared.misc.gms
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.googlePlayUtilityMethod by gettingFirstMethodDeclarativelyOrNull(
"This should never happen.",

View file

@ -14,6 +14,9 @@ import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.firstClassDef
import app.revanced.patcher.firstImmutableClassDef
import app.revanced.patcher.patch.*
import app.revanced.patches.all.misc.packagename.changePackageNamePatch
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.all.misc.resources.addResources

View file

@ -95,18 +95,15 @@ class Replacement(
*/
private fun indexOfPatternIn(haystack: ByteArray): Int {
val needle = bytes
val haystackLength = haystack.size - 1
val needleLength = needle.size - 1
val right = IntArray(256) { -1 }
for (i in 0 until needleLength) right[needle[i].toInt().and(0xFF)] = i
for (i in 0 until needle.size) right[needle[i].toInt().and(0xFF)] = i
var skip: Int
for (i in 0..haystackLength - needleLength) {
for (i in 0..haystack.size - needle.size) {
skip = 0
for (j in needleLength - 1 downTo 0) {
for (j in needle.size - 1 downTo 0) {
if (needle[j] != haystack[i + j]) {
skip = max(1, j - right[haystack[i + j].toInt().and(0xFF)])

View file

@ -4,7 +4,32 @@ import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val BytecodePatchContext.accessibilityIdMethodMatch by composingFirstMethod {
instructions(
allOf(
Opcode.INVOKE_INTERFACE(),
method { parameterTypes.isEmpty() && returnType == "Ljava/lang/String;" }
),
afterAtMost(5, "primary_image"()),
)
}
internal fun BytecodePatchContext.getAccessibilityTextMethodMatch(accessibilityIdMethod: MethodReference) = firstMethodComposite {
returnType("V")
custom {
// 'public final synthetic' or 'public final bridge synthetic'.
AccessFlags.SYNTHETIC.isSet(accessFlags)
}
instructions(
allOf(
Opcode.INVOKE_INTERFACE(),
method { parameterTypes.isEmpty() && returnType == "Ljava/lang/String;" }
),
afterAtMost(5, method { this == accessibilityIdMethod })
)
}
internal val BytecodePatchContext.lithoFilterInitMethod by gettingFirstMethodDeclaratively {
definingClass("/LithoFilterPatch;")
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
@ -40,10 +65,6 @@ internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by getting
opcodes(Opcode.IPUT, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT, Opcode.SUB_INT_2ADDR)
}
internal val BytecodePatchContext.componentContextParserMethodMatch by composingFirstMethod {
instructions("Number of bits must be positive"())
}
internal val BytecodePatchContext.emptyComponentMethod by gettingFirstImmutableMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
parameterTypes()

View file

@ -2,22 +2,36 @@
package app.revanced.patches.shared.misc.litho.filter
import app.revanced.util.getFreeRegisterProvider
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.afterAtMost
import app.revanced.patcher.allOf
import app.revanced.patcher.classDef
import app.revanced.patcher.custom
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.extensions.removeInstructions
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.extensions.typeReference
import app.revanced.patcher.firstImmutableClassDef
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.method
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.findFieldFromToString
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.immutable.value.ImmutableBooleanEncodedValue
/**
@ -156,29 +170,67 @@ internal fun lithoFilterPatch(
type == builderMethodDescriptor.returnType
}.fields.single()
// Match all component creations methods
// Find the method call that gets the value of 'buttonViewModel.accessibilityId'.
val accessibilityIdMethod = accessibilityIdMethodMatch.let {
it.immutableMethod.getInstruction<ReferenceInstruction>(it[0]).methodReference!!
}
// There's a method in the same class that gets the value of 'buttonViewModel.accessibilityText'.
// As this class is abstract, another method that uses a method call is used.
val accessibilityTextMethod = getAccessibilityTextMethodMatch(accessibilityIdMethod).let {
// Find the method call that gets the value of 'buttonViewModel.accessibilityText'.
it.method.getInstruction<ReferenceInstruction>(it[0]).methodReference
}
componentCreateMethod.apply {
val insertIndex = componentCreateInsertionIndex()
val freeRegister = findFreeRegister(insertIndex)
val identifierRegister = findFreeRegister(insertIndex, freeRegister)
val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister)
// Directly access the class related with the buttonViewModel from this method.
// This is within 10 lines of insertIndex.
val buttonViewModelIndex = indexOfFirstInstructionReversedOrThrow(insertIndex) {
opcode == Opcode.CHECK_CAST &&
typeReference?.type == accessibilityIdMethod.definingClass
}
val buttonViewModelRegister =
getInstruction<OneRegisterInstruction>(buttonViewModelIndex).registerA
val accessibilityIdIndex = buttonViewModelIndex + 2
// This is an index that checks if there is accessibility-related text.
// This is within 10 lines of buttonViewModelIndex.
val nullCheckIndex = indexOfFirstInstructionReversedOrThrow(
buttonViewModelIndex, Opcode.IF_EQZ
)
val registerProvider = getFreeRegisterProvider(
insertIndex, 3, buttonViewModelRegister
)
val freeRegister = registerProvider.getFreeRegister()
val identifierRegister = registerProvider.getFreeRegister()
val pathRegister = registerProvider.getFreeRegister()
// Find a free register to store the accessibilityId and accessibilityText.
// This is before the insertion index.
val accessibilityRegisterProvider = getFreeRegisterProvider(
nullCheckIndex,
2,
registerProvider.getUsedAndUnAvailableRegisters()
)
val accessibilityIdRegister = accessibilityRegisterProvider.getFreeRegister()
val accessibilityTextRegister = accessibilityRegisterProvider.getFreeRegister()
addInstructionsAtControlFlowLabel(
insertIndex,
"""
move-object/from16 v$freeRegister, p2 # ConversionContext parameter
# In YT 20.41 the field is the abstract superclass.
# Check it's the actual ConversionContext just in case.
# In YouTube 20.41 the field is the abstract superclass.
# Verify it's the expected subclass just in case.
instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef.type}
if-eqz v$identifierRegister, :unfiltered
# Get identifier and path from ConversionContext
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
# Check if the component should be filtered.
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
invoke-static { v$identifierRegister, v$accessibilityIdRegister, v$accessibilityTextRegister, v$pathRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->isFiltered(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/StringBuilder;)Z
move-result v$freeRegister
if-eqz v$freeRegister, :unfiltered
@ -191,7 +243,31 @@ internal fun lithoFilterPatch(
:unfiltered
nop
""",
"""
)
// If there is text related to accessibility, get the accessibilityId and accessibilityText.
addInstructions(
accessibilityIdIndex,
"""
# Get accessibilityId
invoke-interface { v$buttonViewModelRegister }, $accessibilityIdMethod
move-result-object v$accessibilityIdRegister
# Get accessibilityText
invoke-interface { v$buttonViewModelRegister }, $accessibilityTextMethod
move-result-object v$accessibilityTextRegister
"""
)
// If there is no accessibility-related text,
// both accessibilityId and accessibilityText use empty values.
addInstructions(
nullCheckIndex,
"""
const-string v$accessibilityIdRegister, ""
const-string v$accessibilityTextRegister, ""
"""
)
}

View file

@ -1,27 +1,25 @@
package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.accessFlags
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.name
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.themeLightColorResourceNameMethod by gettingFirstMethodDeclaratively {
context(_: BytecodePatchContext)
internal fun ClassDef.getThemeLightColorResourceNameMethod() = firstMethodDeclaratively {
name("getThemeLightColorResourceName")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Ljava/lang/String;")
parameterTypes()
}
internal val BytecodePatchContext.themeDarkColorResourceNameMethod by gettingFirstMethodDeclaratively {
context(_: BytecodePatchContext)
internal fun ClassDef.getThemeDarkColorResourceNameMethod() = firstMethodDeclaratively {
name("getThemeDarkColorResourceName")
definingClass(EXTENSION_CLASS_DESCRIPTOR)
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returnType("Ljava/lang/String;")
parameterTypes()

View file

@ -1,11 +1,13 @@
package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.firstImmutableClassDef
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResource
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
import app.revanced.patches.shared.layout.branding.addBrandLicensePatch
import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
@ -22,8 +24,8 @@ private var darkThemeColor: String? = null
/**
* Sets the default theme colors used in various ReVanced specific settings menus.
* By default these colors are white and black, but instead can be set to the
* same color the target app uses for it's own settings.
* By default, these colors are white and black, but instead can be set to the
* same color the target app uses for its own settings.
*/
fun overrideThemeColors(lightThemeColorString: String?, darkThemeColorString: String) {
lightThemeColor = lightThemeColorString
@ -32,11 +34,12 @@ fun overrideThemeColors(lightThemeColorString: String?, darkThemeColorString: St
private val settingsColorPatch = bytecodePatch {
afterDependents {
val extensionClassDef = firstImmutableClassDef(EXTENSION_CLASS_DESCRIPTOR)
if (lightThemeColor != null) {
themeLightColorResourceNameMethod.returnEarly(lightThemeColor!!)
extensionClassDef.getThemeLightColorResourceNameMethod().returnEarly(lightThemeColor!!)
}
if (darkThemeColor != null) {
themeDarkColorResourceNameMethod.returnEarly(darkThemeColor!!)
extensionClassDef.getThemeDarkColorResourceNameMethod().returnEarly(darkThemeColor!!)
}
}
}

View file

@ -28,7 +28,7 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/shared/spoof/SpoofVideoStreamsPatch;"
private lateinit var buildRequestMethod: MutableMethod
private var buildRequestMethodUrlRegister = -1
private var buildRequestMethodURLRegister = -1
internal fun spoofVideoStreamsPatch(
extensionClassDescriptor: String,
@ -102,14 +102,14 @@ internal fun spoofVideoStreamsPatch(
buildRequestMethod = this
val newRequestBuilderIndex = buildRequestMethodMatch[0]
buildRequestMethodUrlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodUrlRegister)
buildRequestMethodURLRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodURLRegister)
addInstructions(
newRequestBuilderIndex,
"""
move-object v$freeRegister, p1
invoke-static { v$buildRequestMethodUrlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
invoke-static { v$buildRequestMethodURLRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
""",
)
}
@ -164,7 +164,7 @@ internal fun spoofVideoStreamsPatch(
move-result v0
if-eqz v0, :disabled
# Get video id.
# Get video ID.
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
if-eqz v2, :disabled
@ -202,15 +202,15 @@ internal fun spoofVideoStreamsPatch(
addInstructions(
insertIndex,
"""
invoke-static { v$buildRequestMethodUrlRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$buildRequestMethodUrlRegister
invoke-static { v$buildRequestMethodURLRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$buildRequestMethodURLRegister
""",
)
}
// endregion
// region Remove /videoplayback request body to fix playback.
// region Remove video playback request body to fix playback.
// It is assumed, YouTube makes a request with a body tuned for Android.
// Requesting streams intended for other platforms with a body tuned for Android could be the cause of 400 errors.
// A proper fix may include modifying the request body to match the platforms expected body.

View file

@ -2,23 +2,72 @@ package app.revanced.patches.youtube.ad.general
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.fullScreenEngagementAdContainerMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.fullScreenEngagementAdContainerMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes()
custom {
containsLiteralInstruction(fullScreenEngagementAdContainer) &&
indexOfAddListInstruction(this) >= 0
}
instructions(
ResourceType.ID("fullscreen_engagement_ad_container"),
Opcode.IGET_BOOLEAN(),
allOf(
Opcode.INVOKE_VIRTUAL(),
method {
name == "add" && returnType == "Z"
&& parameterTypes.size == 1 && parameterTypes[0] == "Ljava/lang/Object;"
}
),
allOf(
Opcode.INVOKE_VIRTUAL(),
method {
name == "add" && returnType == "Z"
&& parameterTypes.size == 1 && parameterTypes[0] == "Ljava/lang/Object;"
}
),
allOf(
Opcode.INVOKE_VIRTUAL(),
method { name == "size" && returnType == "I" && parameterTypes.isEmpty() }
),
)
}
internal fun indexOfAddListInstruction(method: Method) = method.indexOfFirstInstructionReversed {
getReference<MethodReference>()?.name == "add"
internal val BytecodePatchContext.getPremiumViewMethodMatch by composingFirstMethod {
name("onMeasure")
definingClass("Lcom/google/android/apps/youtube/app/red/presenter/CompactYpcOfferModuleView;")
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returnType("V")
parameterTypes("I", "I")
opcodes(
Opcode.ADD_INT_2ADDR,
Opcode.ADD_INT_2ADDR,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
)
}
internal val BytecodePatchContext.lithoDialogBuilderMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("[B", "L")
instructions(
allOf(Opcode.INVOKE_VIRTUAL(), method("show")),
ResourceType.STYLE("SlidingDialogAnimation")
)
}
internal val BytecodePatchContext.playerOverlayTimelyShelfMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("Ljava/lang/Object;")
instructions(
"player_overlay_timely_shelf"(),
"innertube_cue_range"(),
"Null id"(),
"Null onExitActions"()
)
}

View file

@ -1,10 +1,12 @@
package app.revanced.patches.youtube.ad.general
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.fieldReference
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.extensions.wideLiteral
import app.revanced.patcher.firstMethod
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
@ -13,23 +15,32 @@ import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.contexthook.Endpoint
import app.revanced.patches.youtube.misc.contexthook.addOSNameHook
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch
import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch
import app.revanced.patches.youtube.misc.engagement.addEngagementPanelIdHook
import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.forEachInstructionAsSequence
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.injectHideViewCall
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/litho/AdsFilter;"
internal var adAttributionId = -1L
private set
internal var fullScreenEngagementAdContainer = -1L
private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/litho/AdsFilter;"
private val hideAdsResourcePatch = resourcePatch {
dependsOn(
@ -37,28 +48,30 @@ private val hideAdsResourcePatch = resourcePatch {
settingsPatch,
resourceMappingPatch,
addResourcesPatch,
hookClientContextPatch,
engagementPanelHookPatch,
)
apply {
addResources("youtube", "ad.general.hideAdsResourcePatch")
PreferenceScreen.ADS.addPreferences(
SwitchPreference("revanced_hide_creator_store_shelf"),
SwitchPreference("revanced_hide_end_screen_store_banner"),
SwitchPreference("revanced_hide_fullscreen_ads"),
SwitchPreference("revanced_hide_general_ads"),
SwitchPreference("revanced_hide_merchandise_banners"),
SwitchPreference("revanced_hide_paid_promotion_label"),
SwitchPreference("revanced_hide_player_popup_ads"),
SwitchPreference("revanced_hide_self_sponsor_ads"),
SwitchPreference("revanced_hide_shopping_links"),
SwitchPreference("revanced_hide_view_products_banner"),
SwitchPreference("revanced_hide_web_search_results"),
SwitchPreference("revanced_hide_youtube_premium_promotions")
)
addLithoFilter("Lapp/revanced/extension/youtube/patches/litho/AdsFilter;")
addEngagementPanelIdHook("$EXTENSION_CLASS_DESCRIPTOR->hidePlayerPopupAds(Ljava/lang/String;)Z")
adAttributionId = ResourceType.ID["ad_attribution"]
fullScreenEngagementAdContainer = ResourceType.ID["fullscreen_engagement_ad_container"]
}
}
@ -68,35 +81,109 @@ val hideAdsPatch = bytecodePatch(
description = "Adds options to remove general ads.",
) {
dependsOn(
hideGetPremiumPatch,
hideAdsResourcePatch,
verticalScrollPatch,
fixBackToExitGesturePatch,
versionCheckPatch
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
// Hide fullscreen ad
lithoDialogBuilderMethodMatch.let {
it.method.apply {
// Find the class name of the custom dialog
val dialogClass = getInstruction(it[0]).methodReference!!.definingClass
// The dialog can be closed after dialog.show(),
// and it is better to close the dialog after the layout of the dialog has changed
val insertIndex = indexOfFirstInstructionReversedOrThrow {
opcode == Opcode.IPUT_OBJECT && fieldReference?.type == dialogClass
}
val insertRegister =
getInstruction<TwoRegisterInstruction>(insertIndex).registerA
val freeRegister = findFreeRegister(insertIndex, insertRegister)
addInstructionsAtControlFlowLabel(
insertIndex,
"""
move-object/from16 v$freeRegister, p1
invoke-static { v$insertRegister, v$freeRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->closeFullscreenAd(Ljava/lang/Object;[B)V
"""
)
}
}
// Hide get premium
getPremiumViewMethodMatch.method.apply {
val startIndex = getPremiumViewMethodMatch[0]
val measuredWidthRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
val measuredHeightInstruction = getInstruction<TwoRegisterInstruction>(startIndex + 1)
val measuredHeightRegister = measuredHeightInstruction.registerA
val tempRegister = measuredHeightInstruction.registerB
addInstructionsWithLabels(
startIndex + 2,
"""
# Override the internal measurement of the layout with zero values.
invoke-static {}, ${EXTENSION_CLASS_DESCRIPTOR}->hideGetPremiumView()Z
move-result v$tempRegister
if-eqz v$tempRegister, :allow
const/4 v$measuredWidthRegister, 0x0
const/4 v$measuredHeightRegister, 0x0
:allow
nop
# Layout width/height is then passed to a protected class method.
""",
)
}
// Hide player overlay view. This can be hidden with a regular litho filter
// but an empty space remains.
if (is_20_14_or_greater) {
playerOverlayTimelyShelfMethod.addInstructionsWithLabels(
0,
"""
invoke-static {}, ${EXTENSION_CLASS_DESCRIPTOR}->hideAds()Z
move-result v0
if-eqz v0, :show
return-void
:show
nop
"""
)
}
// Hide end screen store banner.
fullScreenEngagementAdContainerMethod.apply {
val addListIndex = indexOfAddListInstruction(this)
val addListInstruction = getInstruction<FiveRegisterInstruction>(addListIndex)
val listRegister = addListInstruction.registerC
val objectRegister = addListInstruction.registerD
fullScreenEngagementAdContainerMethodMatch.let {
it.method.apply {
val insertIndex = it[3]
val insertInstruction = getInstruction<FiveRegisterInstruction>(insertIndex)
val listRegister = insertInstruction.registerC
val objectRegister = insertInstruction.registerD
replaceInstruction(
addListIndex,
"invoke-static { v$listRegister, v$objectRegister }, $EXTENSION_CLASS_DESCRIPTOR" +
"->hideEndScreenStoreBanner(Ljava/util/List;Ljava/lang/Object;)V",
)
replaceInstruction(
insertIndex,
"invoke-static { v$listRegister, v$objectRegister }, " +
"${EXTENSION_CLASS_DESCRIPTOR}->" +
"hideEndScreenStoreBanner(Ljava/util/List;Ljava/lang/Object;)V"
)
}
}
// Hide ad views.
@ -114,7 +201,22 @@ val hideAdsPatch = bytecodePatch(
return@forEachInstructionAsSequence insertIndex to viewRegister
}) { method, (insertIndex, viewRegister) ->
method.injectHideViewCall(insertIndex, viewRegister, EXTENSION_CLASS_DESCRIPTOR, "hideAdAttributionView")
method.injectHideViewCall(
insertIndex,
viewRegister,
EXTENSION_CLASS_DESCRIPTOR,
"hideAdAttributionView"
)
}
setOf(
Endpoint.BROWSE,
Endpoint.SEARCH,
).forEach { endpoint ->
addOSNameHook(
endpoint,
"$EXTENSION_CLASS_DESCRIPTOR->hideAds(Ljava/lang/String;)Ljava/lang/String;",
)
}
}
}

View file

@ -1,20 +0,0 @@
package app.revanced.patches.youtube.ad.getpremium
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.getPremiumViewMethodMatch by composingFirstMethod {
name("onMeasure")
definingClass("Lcom/google/android/apps/youtube/app/red/presenter/CompactYpcOfferModuleView;")
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returnType("V")
parameterTypes("I", "I")
opcodes(
Opcode.ADD_INT_2ADDR,
Opcode.ADD_INT_2ADDR,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID,
)
}

View file

@ -1,66 +0,0 @@
package app.revanced.patches.youtube.ad.getpremium
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideGetPremiumPatch;"
val hideGetPremiumPatch = bytecodePatch(
description = "Hides YouTube Premium signup promotions under the video player.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
),
)
apply {
addResources("youtube", "ad.getpremium.hideGetPremiumPatch")
PreferenceScreen.ADS.addPreferences(
SwitchPreference("revanced_hide_get_premium"),
)
getPremiumViewMethodMatch.let {
val startIndex = it[0]
val measuredWidthRegister = it.method.getInstruction<TwoRegisterInstruction>(startIndex).registerA
val measuredHeightInstruction = it.method.getInstruction<TwoRegisterInstruction>(startIndex + 1)
val measuredHeightRegister = measuredHeightInstruction.registerA
val tempRegister = measuredHeightInstruction.registerB
it.method.addInstructionsWithLabels(
startIndex + 2,
"""
# Override the internal measurement of the layout with zero values.
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideGetPremiumView()Z
move-result v$tempRegister
if-eqz v$tempRegister, :allow
const/4 v$measuredWidthRegister, 0x0
const/4 v$measuredHeightRegister, 0x0
:allow
nop
# Layout width/height is then passed to a protected class method.
""",
)
}
}
}

View file

@ -24,10 +24,12 @@ val videoAdsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -12,7 +12,7 @@ import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
private val copyVideoUrlResourcePatch = resourcePatch {
private val copyVideoURLResourcePatch = resourcePatch {
dependsOn(
settingsPatch,
playerControlsPatch,
@ -20,7 +20,7 @@ private val copyVideoUrlResourcePatch = resourcePatch {
)
apply {
addResources("youtube", "interaction.copyvideourl.copyVideoUrlResourcePatch")
addResources("youtube", "interaction.copyvideourl.copyVideoURLResourcePatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_copy_video_url"),
@ -46,25 +46,27 @@ val copyVideoURLPatch = bytecodePatch(
description = "Adds options to display buttons in the video player to copy video URLs.",
) {
dependsOn(
copyVideoUrlResourcePatch,
copyVideoURLResourcePatch,
playerControlsPatch,
videoInformationPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
val extensionPlayerPackage = "Lapp/revanced/extension/youtube/videoplayer"
val buttonsDescriptors = listOf(
"$extensionPlayerPackage/CopyVideoUrlButton;",
"$extensionPlayerPackage/CopyVideoUrlTimestampButton;",
"$extensionPlayerPackage/CopyVideoURLButton;",
"$extensionPlayerPackage/CopyVideoURLTimestampButton;",
)
buttonsDescriptors.forEach { descriptor ->

View file

@ -2,6 +2,8 @@ package app.revanced.patches.youtube.interaction.dialog
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.createDialogMethodMatch by composingFirstMethod {
returnType("V")
@ -13,3 +15,28 @@ internal val BytecodePatchContext.createDialogMethodMatch by composingFirstMetho
method { toString() == "Landroid/app/AlertDialog;->show()V" },
)
}
internal val BytecodePatchContext.createModernDialogMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes()
instructions(
Opcode.MOVE_RESULT(),
method { toString() == $$"Landroid/app/AlertDialog$Builder;->setIcon(I)Landroid/app/AlertDialog$Builder;" },
method { toString() == $$"Landroid/app/AlertDialog$Builder;->create()Landroid/app/AlertDialog;" }
)
}
internal val BytecodePatchContext.playabilityStatusEnumMethod by gettingFirstImmutableMethod(
"OK",
"ERROR",
"UNPLAYABLE",
"LOGIN_REQUIRED",
"CONTENT_CHECK_REQUIRED",
"AGE_CHECK_REQUIRED",
"LIVE_STREAM_OFFLINE",
"FULLSCREEN_ONLY",
"GL_PLAYBACK_REQUIRED",
"AGE_VERIFICATION_REQUIRED",
)

View file

@ -1,15 +1,28 @@
package app.revanced.patches.youtube.interaction.dialog
import app.revanced.patcher.accessFlags
import app.revanced.patcher.custom
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.replaceInstructions
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.returnType
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.backgroundPlaybackManagerShortsMethod
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/RemoveViewerDiscretionDialogPatch;"
@ -18,7 +31,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val removeViewerDiscretionDialogPatch = bytecodePatch(
name = "Remove viewer discretion dialog",
description = "Adds an option to remove the dialog that appears when opening a video that has been age-restricted " +
"by accepting it automatically. This does not bypass the age restriction.",
"by accepting it automatically. This does not bypass the age restriction.",
) {
dependsOn(
sharedExtensionPatch,
@ -28,10 +41,12 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -45,13 +60,56 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
createDialogMethodMatch.let {
it.method.apply {
val showDialogIndex = it[-1]
val dialogRegister = getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC
val dialogRegister =
getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC
replaceInstructions(
showDialogIndex,
"invoke-static { v$dialogRegister }, $EXTENSION_CLASS_DESCRIPTOR->confirmDialog(Landroid/app/AlertDialog;)V",
"invoke-static { v$dialogRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->" +
"confirmDialog(Landroid/app/AlertDialog;)V",
)
}
}
createModernDialogMethodMatch.let {
it.method.apply {
val showDialogIndex = it[-1]
val dialogRegister =
getInstruction<FiveRegisterInstruction>(showDialogIndex).registerC
replaceInstructions(
showDialogIndex,
"invoke-static { v$dialogRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->" +
"confirmDialog(Landroid/app/AlertDialog\$Builder;)Landroid/app/AlertDialog;",
)
val dialogStyleIndex = it[0]
val dialogStyleRegister =
getInstruction<OneRegisterInstruction>(dialogStyleIndex).registerA
addInstructions(
dialogStyleIndex + 1,
"""
invoke-static { v$dialogStyleRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->disableModernDialog(Z)Z
move-result v$dialogStyleRegister
"""
)
}
}
backgroundPlaybackManagerShortsMethod.immutableClassDef.firstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returnType("Z")
parameterTypes(playabilityStatusEnumMethod.immutableClassDef.type)
custom {
// There's another similar method that's difficult to match uniquely,
// Instruction counter is used to identify the target method.
instructions.count() < 10
}
}.addInstruction(
0,
"invoke-static { p0 }, ${EXTENSION_CLASS_DESCRIPTOR}->" +
"setPlayabilityStatus(Ljava/lang/Enum;)V"
)
}
}

View file

@ -0,0 +1,66 @@
package app.revanced.patches.youtube.interaction.doubletap
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.removeFromParent
import org.w3c.dom.Element
@Suppress("unused")
val addMoreDoubleTapToSeekLengthOptionsPatch = resourcePatch(
name = "Add more double tap to seek length options",
) {
dependsOn(
sharedExtensionPatch
)
compatibleWith(
"com.google.android.youtube"(
"20.14.43",
"20.21.37",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
)
)
execute {
// Values are hard coded to keep patching simple.
val doubleTapLengthOptionsString = "3, 5, 10, 15, 20, 30, 60, 120, 180, 240"
val doubleTapLengths = doubleTapLengthOptionsString
.replace(" ", "")
.split(",")
if (doubleTapLengths.isEmpty()) throw PatchException("Invalid double-tap length elements")
document("res/values/arrays.xml").use { document ->
fun Element.removeAllChildren() {
val children = childNodes // Calling childNodes creates a new list.
for (i in children.length - 1 downTo 0) {
children.item(i).removeFromParent()
}
}
val values = document.childNodes.findElementByAttributeValueOrThrow(
attributeName = "name",
value = "double_tap_length_values"
)
values.removeAllChildren()
val entries = document.childNodes.findElementByAttributeValueOrThrow(
attributeName = "name",
value = "double_tap_length_entries"
)
entries.removeAllChildren()
doubleTapLengths.forEach { length ->
val item = document.createElement("item")
item.textContent = length
entries.appendChild(item)
values.appendChild(item.cloneNode(true))
}
}
}
}

View file

@ -34,7 +34,10 @@ val disableDoubleTapActionsPatch = bytecodePatch(
"com.google.android.youtube"(
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -77,12 +80,13 @@ val disableDoubleTapActionsPatch = bytecodePatch(
""",
)
doubleTapInfoGetSeekSourceMethod.immutableClassDef.getDoubleTapInfoCtorMethod().addInstructions(
0,
"""
doubleTapInfoGetSeekSourceMethod.immutableClassDef.getDoubleTapInfoCtorMethod()
.addInstructions(
0,
"""
invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->disableDoubleTapChapters(Z)Z
move-result p3
""",
)
)
}
}

View file

@ -55,15 +55,17 @@ private val downloadsResourcePatch = resourcePatch {
}
}
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/DownloadsPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DownloadsPatch;"
internal const val BUTTON_DESCRIPTOR = "Lapp/revanced/extension/youtube/videoplayer/ExternalDownloadButton;"
internal const val BUTTON_DESCRIPTOR =
"Lapp/revanced/extension/youtube/videoplayer/ExternalDownloadButton;"
@Suppress("unused")
val downloadsPatch = bytecodePatch(
name = "Downloads",
description = "Adds support to download videos with an external downloader app " +
"using the in-app download button or a video player action button.",
"using the in-app download button or a video player action button.",
) {
dependsOn(
downloadsResourcePatch,
@ -73,10 +75,12 @@ val downloadsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -10,7 +10,7 @@ internal val BytecodePatchContext.offlineVideoEndpointMethod by gettingFirstMeth
parameterTypes(
"Ljava/util/Map;",
"L",
"Ljava/lang/String", // VideoId
"Ljava/lang/String", // Video ID
"L",
)
instructions(

View file

@ -0,0 +1,139 @@
package app.revanced.patches.youtube.interaction.hapticfeedback
import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.fieldReference
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/extension/youtube/patches/DisableHapticFeedbackPatch"
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
@Suppress("unused")
val disableHapticFeedbackPatch = bytecodePatch(
name = "Disable haptic feedback",
description = "Adds an option to disable haptic feedback in the player for various actions.",
) {
dependsOn(
settingsPatch,
addResourcesPatch,
transformInstructionsPatch(
filterMap = { classDef, _, instruction, instructionIndex ->
filterMapInstruction35c<MethodCall>(
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex,
)
},
transform = { method, entry ->
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithExtension(
EXTENSION_CLASS_DESCRIPTOR,
method,
instruction,
instructionIndex,
)
},
),
)
compatibleWith(
"com.google.android.youtube"(
"20.14.43",
"20.21.37",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
addResources("youtube", "misc.hapticfeedback.disableHapticFeedbackPatch")
PreferenceScreen.PLAYER.addPreferences(
PreferenceScreenPreference(
"revanced_disable_haptic_feedback",
preferences = setOf(
SwitchPreference("revanced_disable_haptic_feedback_chapters"),
SwitchPreference("revanced_disable_haptic_feedback_precise_seeking"),
SwitchPreference("revanced_disable_haptic_feedback_seek_undo"),
SwitchPreference("revanced_disable_haptic_feedback_tap_and_hold"),
SwitchPreference("revanced_disable_haptic_feedback_zoom"),
),
),
)
arrayOf(
markerHapticsMethod to "disableChapterVibrate",
scrubbingHapticsMethod to "disablePreciseSeekingVibrate",
seekUndoHapticsMethod to "disableSeekUndoVibrate",
zoomHapticsMethod to "disableZoomVibrate",
).forEach { (method, methodName) ->
method.addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->$methodName()Z
move-result v0
if-eqz v0, :vibrate
return-void
""",
ExternalLabel("vibrate", method.getInstruction(0)),
)
}
val vibratorField = tapAndHoldHapticsHandlerMethodMatch.let {
it.immutableMethod.getInstruction(it[-1]).fieldReference!!
}
// Function, because it can be the same method as getTapAndHoldSpeedMethodMatch.
getTapAndHoldHapticsMethodMatch(vibratorField).let {
it.method.apply {
val index = it[0]
val register = getInstruction<TwoRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->disableTapAndHoldVibrate(Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v$register
"""
)
}
}
}
}
@Suppress("unused")
private enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String,
) : IMethodCall {
VibrationEffect(
"Landroid/os/Vibrator;",
"vibrate",
arrayOf("Landroid/os/VibrationEffect;"),
"V",
),
VibrationMilliseconds(
"Landroid/os/Vibrator;",
"vibrate",
arrayOf("J"),
"V",
),
}

View file

@ -0,0 +1,56 @@
package app.revanced.patches.youtube.interaction.hapticfeedback
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal val BytecodePatchContext.markerHapticsMethod by gettingFirstMethodDeclaratively(
"Failed to execute markers haptics vibrate.",
) {
returnType("V")
}
internal val BytecodePatchContext.scrubbingHapticsMethod by gettingFirstMethodDeclaratively(
"Failed to haptics vibrate for fine scrubbing.",
) {
returnType("V")
}
internal val BytecodePatchContext.seekUndoHapticsMethod by gettingFirstMethodDeclaratively(
"Failed to execute seek undo haptics vibrate.",
) {
returnType("V")
}
internal val BytecodePatchContext.tapAndHoldHapticsHandlerMethodMatch by composingFirstMethod {
name("<init>")
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
returnType("V")
parameterTypes("Landroid/content/Context;", "Landroid/os/Handler;")
instructions(
"vibrator"(),
allOf(Opcode.CHECK_CAST(), type("Landroid/os/Vibrator;")),
after(allOf(Opcode.IPUT_OBJECT(), field { type == "Ljava/lang/Object;" }))
)
}
internal fun BytecodePatchContext.getTapAndHoldHapticsMethodMatch(vibratorFieldReference: FieldReference) =
firstMethodComposite {
name("run")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes()
instructions(
allOf(Opcode.IGET_OBJECT(), field { this == vibratorFieldReference }),
allOf(Opcode.CHECK_CAST(), type("Landroid/os/Vibrator;")),
"Failed to easy seek haptics vibrate."()
)
}
internal val BytecodePatchContext.zoomHapticsMethod by gettingFirstMethodDeclaratively(
"Failed to haptics vibrate for video zoom",
) {
returnType("V")
}

View file

@ -41,7 +41,7 @@ val enableSlideToSeekPatch = bytecodePatch(
var modifiedMethods = false
// Restore the behaviour to slide to seek.
// Restore the behavior to slide to seek.
val checkIndex = slideToSeekMethodMatch[0]
val checkReference = slideToSeekMethodMatch.method.getInstruction(checkIndex)

View file

@ -15,9 +15,9 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/SeekbarTappingPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/TapToSeekPatch;"
val enableSeekbarTappingPatch = bytecodePatch(
val enableTapToSeekPatch = bytecodePatch(
description = "Adds an option to enable tap to seek on the seekbar of the video player.",
) {
dependsOn(
@ -27,14 +27,14 @@ val enableSeekbarTappingPatch = bytecodePatch(
)
apply {
addResources("youtube", "interaction.seekbar.enableSeekbarTappingPatch")
addResources("youtube", "interaction.seekbar.enableTapToSeekPatch")
PreferenceScreen.SEEKBAR.addPreferences(
SwitchPreference("revanced_seekbar_tapping"),
SwitchPreference("revanced_tap_to_seek"),
)
// Find the required methods to tap the seekbar.
val seekbarTappingMethods = onTouchEventHandlerMethodMatch.let {
val tapToSeekMethods = onTouchEventHandlerMethodMatch.let {
fun getReference(index: Int) = it.method.getInstruction<ReferenceInstruction>(index)
.reference as MethodReference
@ -44,7 +44,7 @@ val enableSeekbarTappingPatch = bytecodePatch(
)
}
seekbarTappingMethodMatch.let {
tapToSeekMethodMatch.let {
val insertIndex = it[-1] + 1
it.method.apply {
@ -62,13 +62,13 @@ val enableSeekbarTappingPatch = bytecodePatch(
xAxisRegister,
)
val oMethod = seekbarTappingMethods[0]
val nMethod = seekbarTappingMethods[1]
val oMethod = tapToSeekMethods[0]
val nMethod = tapToSeekMethods[1]
addInstructionsWithLabels(
insertIndex,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->seekbarTappingEnabled()Z
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->tapToSeekEnabled()Z
move-result v$freeRegister
if-eqz v$freeRegister, :disabled
invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $oMethod

View file

@ -2,10 +2,7 @@ package app.revanced.patches.youtube.interaction.seekbar
import app.revanced.patcher.*
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.extensions.stringReference
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.youtube.misc.playservice.*
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@ -61,25 +58,6 @@ internal val BytecodePatchContext.disableFastForwardGestureMethodMatch by compos
custom { instructions.count() > 30 }
}
internal val BytecodePatchContext.customTapAndHoldMethodMatch by composingFirstMethod {
name("run")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes()
instructions(2.0f.toRawBits().toLong()())
custom {
// Code is found in different methods with different strings.
val findSearchLandingKey = (is_19_34_or_greater && !is_19_47_or_greater) ||
(is_20_19_or_greater && !is_20_20_or_greater) || is_20_31_or_greater
indexOfFirstInstruction {
val string = stringReference?.string
string == "Failed to easy seek haptics vibrate." ||
(findSearchLandingKey && string == "search_landing_cache_key")
} >= 0
}
}
internal val BytecodePatchContext.onTouchEventHandlerMethodMatch by composingFirstMethod {
name("onTouchEvent")
accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC)
@ -103,7 +81,7 @@ internal val BytecodePatchContext.onTouchEventHandlerMethodMatch by composingFir
)
}
internal val BytecodePatchContext.seekbarTappingMethodMatch by composingFirstMethod {
internal val BytecodePatchContext.tapToSeekMethodMatch by composingFirstMethod {
name("onTouchEvent")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
@ -132,15 +110,6 @@ internal val BytecodePatchContext.slideToSeekMethodMatch by composingFirstMethod
literal { 67108864 }
}
internal val BytecodePatchContext.fullscreenSeekbarThumbnailsQualityMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
parameterTypes()
instructions(
45399684L(), // Video stream seekbar thumbnails feature flag.
)
}
internal val BytecodePatchContext.fullscreenLargeSeekbarFeatureFlagMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")

View file

@ -6,24 +6,24 @@ import app.revanced.patcher.patch.bytecodePatch
val seekbarPatch = bytecodePatch(
name = "Seekbar",
description = "Adds options to disable precise seeking when swiping up on the seekbar, " +
"slide to seek instead of playing at 2x speed when pressing and holding, " +
"tapping the player seekbar to seek, " +
"and hiding the video player seekbar.",
"slide to seek instead of playing at 2x speed when pressing and holding, " +
"tapping the player seekbar to seek, and hiding the video player seekbar.",
) {
dependsOn(
disablePreciseSeekingGesturePatch,
enableSlideToSeekPatch,
enableSeekbarTappingPatch,
enableTapToSeekPatch,
hideSeekbarPatch,
seekbarThumbnailsPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
}

View file

@ -1,71 +0,0 @@
package app.revanced.patches.youtube.interaction.seekbar
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.layout.seekbar.fullscreenSeekbarThumbnailsMethod
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/SeekbarThumbnailsPatch;"
val seekbarThumbnailsPatch = bytecodePatch(
description = "Adds an option to use high quality fullscreen seekbar thumbnails.",
) {
dependsOn(
sharedExtensionPatch,
addResourcesPatch,
versionCheckPatch,
)
apply {
if (is_20_09_or_greater) {
// High quality seekbar thumbnails is partially broken in 20.09
// and the code is completely removed in 20.10+
return@apply
}
addResources("youtube", "layout.seekbar.seekbarThumbnailsPatch")
if (is_19_17_or_greater) {
PreferenceScreen.SEEKBAR.addPreferences(
SwitchPreference("revanced_seekbar_thumbnails_high_quality"),
)
} else {
PreferenceScreen.SEEKBAR.addPreferences(
SwitchPreference("revanced_restore_old_seekbar_thumbnails"),
SwitchPreference(
key = "revanced_seekbar_thumbnails_high_quality",
summaryOnKey = "revanced_seekbar_thumbnails_high_quality_legacy_summary_on",
summaryOffKey = "revanced_seekbar_thumbnails_high_quality_legacy_summary_on",
),
)
fullscreenSeekbarThumbnailsMethod.apply {
val moveResultIndex = instructions.lastIndex - 1
addInstruction(
moveResultIndex,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->useFullscreenSeekbarThumbnails()Z",
)
}
}
fullscreenSeekbarThumbnailsQualityMethod.addInstructions(
0,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->useHighQualityFullscreenThumbnails()Z
move-result v0
return v0
""",
)
}
}

View file

@ -53,7 +53,10 @@ private val swipeControlsResourcePatch = resourcePatch {
SwitchPreference("revanced_swipe_save_and_restore_brightness"),
SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"),
ListPreference("revanced_swipe_overlay_style"),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference(
"revanced_swipe_overlay_background_opacity",
inputType = InputType.NUMBER
),
TextPreference(
"revanced_swipe_overlay_progress_brightness_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerWithOpacitySliderPreference",
@ -101,10 +104,12 @@ val swipeControlsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -8,6 +8,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.subtitleButtonControllerMethod
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableAutoCaptionsPatch;"
@ -25,10 +26,12 @@ val disableAutoCaptionsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -54,7 +57,7 @@ val disableAutoCaptionsPatch = bytecodePatch(
arrayOf(
startVideoInformerMethod to 0,
storyboardRendererDecoderRecommendedLevelMethod to 1,
subtitleButtonControllerMethod to 1,
).forEach { (method, enabled) ->
method.addInstructions(
0,

View file

@ -14,23 +14,8 @@ internal val BytecodePatchContext.startVideoInformerMethod by gettingFirstMethod
)
}
internal val BytecodePatchContext.storyboardRendererDecoderRecommendedLevelMethod by gettingFirstMethodDeclaratively("#-1#") {
returnType("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes("L")
}
internal val BytecodePatchContext.subtitleTrackMethod by gettingFirstMethodDeclaratively("DISABLE_CAPTIONS_OPTION") {
definingClass("/SubtitleTrack;")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
parameterTypes()
opcodes(
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.RETURN,
)
}

View file

@ -26,10 +26,12 @@ val customBrandingPatch = baseCustomBrandingPatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
},

View file

@ -53,7 +53,8 @@ private val customHeaderResourceFileNames = variants.map { variant ->
"${CUSTOM_HEADER_RESOURCE_NAME}_$variant.png"
}.toTypedArray()
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeHeaderPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ChangeHeaderPatch;"
private val changeHeaderBytecodePatch = bytecodePatch {
dependsOn(
@ -106,10 +107,12 @@ val changeHeaderPatch = resourcePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -16,7 +16,8 @@ import java.util.logging.Logger
@Suppress("unused")
val hideVideoActionButtonsPatch = resourcePatch(
name = "Hide video action buttons",
description = "Adds options to hide action buttons (such as the Download button) under videos.",
description = "Adds options to hide action buttons (such as the Download button) under videos. " +
"Patching version 20.21.37 or lower can hide more player button types."
) {
dependsOn(
resourceMappingPatch,
@ -27,10 +28,12 @@ val hideVideoActionButtonsPatch = resourcePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
// 20.22+ does not yet support hiding all player buttons.
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -43,27 +46,20 @@ val hideVideoActionButtonsPatch = resourcePatch(
SwitchPreference("revanced_hide_like_dislike_button"),
SwitchPreference("revanced_hide_comments_button"),
SwitchPreference("revanced_hide_save_button"),
)
SwitchPreference("revanced_hide_remix_button"),
SwitchPreference("revanced_hide_share_button"),
if (is_20_22_or_greater) {
// FIXME: 20.22+ filtering of the action buttons doesn't work because
// the buffer is the same for all buttons.
Logger.getLogger(this::class.java.name).warning(
"\n!!!" +
"\n!!! Not all player action buttons can be set hidden when patching 20.22+" +
"\n!!! Patch 20.21.37 or lower if you want to hide player action buttons" +
"\n!!!",
)
} else {
// 20.22+ cannot hide all action buttons because of buffer changes.
if (!is_20_22_or_greater) {
preferences.addAll(
listOf(
SwitchPreference("revanced_hide_hype_button"),
SwitchPreference("revanced_hide_ask_button"),
SwitchPreference("revanced_hide_clip_button"),
SwitchPreference("revanced_hide_promote_button"),
SwitchPreference("revanced_hide_remix_button"),
SwitchPreference("revanced_hide_report_button"),
SwitchPreference("revanced_hide_share_button"),
SwitchPreference("revanced_hide_shop_button"),
SwitchPreference("revanced_hide_stop_ads_button"),
SwitchPreference("revanced_hide_thanks_button"),
@ -78,6 +74,6 @@ val hideVideoActionButtonsPatch = resourcePatch(
),
)
addLithoFilter("Lapp/revanced/extension/youtube/patches/litho/ButtonsFilter;")
addLithoFilter("Lapp/revanced/extension/youtube/patches/litho/VideoActionButtonsFilter;")
}
}

View file

@ -2,17 +2,10 @@ package app.revanced.patches.youtube.layout.buttons.navigation
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.addCreateButtonViewMethodMatch by composingFirstMethod {
instructions(
"Android Wear"(),
Opcode.IF_EQZ(),
after("Android Automotive"()),
)
}
internal val BytecodePatchContext.createPivotBarMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes(
@ -34,6 +27,28 @@ internal val BytecodePatchContext.animatedNavigationTabsFeatureFlagMethodMatch b
)
}
internal val BytecodePatchContext.pivotBarStyleMethodMatch by composingFirstMethod {
definingClass("/PivotBar;")
returnType("V")
parameterTypes("L")
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
Opcode.XOR_INT_2ADDR
)
}
internal val BytecodePatchContext.pivotBarChangedMethodMatch by composingFirstMethod {
name("onConfigurationChanged")
definingClass("/PivotBar;")
returnType("V")
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT
)
}
internal val BytecodePatchContext.translucentNavigationStatusBarFeatureFlagMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
@ -63,3 +78,20 @@ internal val BytecodePatchContext.translucentNavigationButtonsSystemFeatureFlagM
45632194L(), // Translucent system buttons feature flag.
)
}
internal val BytecodePatchContext.setWordmarkHeaderMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("Landroid/widget/ImageView;")
instructions(
ResourceType.ATTR("ytPremiumWordmarkHeader"),
ResourceType.ATTR("ytWordmarkHeader")
)
}
internal val BytecodePatchContext.wideSearchbarLayoutMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
parameterTypes("L", "L")
instructions(ResourceType.LAYOUT("action_bar_ringo"))
}

View file

@ -0,0 +1,253 @@
package app.revanced.patches.youtube.layout.buttons.navigation
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.layout.toolbar.hookToolbar
import app.revanced.patches.youtube.layout.toolbar.toolbarHookPatch
import app.revanced.patches.youtube.misc.contexthook.Endpoint
import app.revanced.patches.youtube.misc.contexthook.addOSNameHook
import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_15_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_31_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
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.reference.MethodReference
import kotlin.collections.plusAssign
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/NavigationBarPatch;"
val navigationBarPatch = bytecodePatch(
name = "Navigation bar",
description = "Adds options to hide and change the bottom navigation bar (such as the Shorts button) "
+ " and the upper navigation toolbar. Patching version 20.21.37 and lower also adds a setting to use a wide searchbar."
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
navigationBarHookPatch,
versionCheckPatch,
hookClientContextPatch,
toolbarHookPatch
)
compatibleWith(
"com.google.android.youtube"(
"20.14.43",
"20.21.37",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
addResources("youtube", "layout.buttons.navigation.navigationBarPatch")
val preferences = mutableSetOf(
SwitchPreference("revanced_hide_home_button"),
SwitchPreference("revanced_hide_shorts_button"),
SwitchPreference("revanced_hide_create_button"),
SwitchPreference("revanced_hide_subscriptions_button"),
SwitchPreference("revanced_hide_notifications_button"),
SwitchPreference("revanced_switch_create_with_notifications_button"),
SwitchPreference("revanced_hide_navigation_button_labels"),
SwitchPreference("revanced_narrow_navigation_buttons"),
)
if (is_19_25_or_greater) {
preferences += SwitchPreference("revanced_disable_translucent_navigation_bar_light")
preferences += SwitchPreference("revanced_disable_translucent_navigation_bar_dark")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_disable_translucent_status_bar")
)
if (is_20_15_or_greater) {
preferences += SwitchPreference("revanced_navigation_bar_animations")
}
}
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
PreferenceScreenPreference(
key = "revanced_navigation_buttons_screen",
sorting = Sorting.UNSORTED,
preferences = preferences
)
)
// Switch create with notifications button.
addOSNameHook(
Endpoint.GUIDE,
"${EXTENSION_CLASS_DESCRIPTOR}->switchCreateWithNotificationButton(Ljava/lang/String;)Ljava/lang/String;",
)
// Hide navigation button labels.
createPivotBarMethodMatch.let {
it.method.apply {
val setTextIndex = it[0]
val targetRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC
addInstruction(
setTextIndex,
"invoke-static { v$targetRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->hideNavigationButtonLabels(Landroid/widget/TextView;)V",
)
}
}
// Hook navigation button created, in order to hide them.
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
// Force on/off translucent effect on status bar and navigation buttons.
if (is_19_25_or_greater) {
translucentNavigationStatusBarFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationStatusBar(Z)Z",
)
}
translucentNavigationButtonsFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
)
}
translucentNavigationButtonsSystemFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
)
}
}
if (is_20_15_or_greater) {
animatedNavigationTabsFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useAnimatedNavigationButtons(Z)Z",
)
}
}
arrayOf(
pivotBarChangedMethodMatch,
pivotBarStyleMethodMatch
).forEach { match ->
match.method.apply {
val targetIndex = match[1] + 1
val register = getInstruction<OneRegisterInstruction>(targetIndex - 1).registerA
addInstructions(
targetIndex,
"""
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->enableNarrowNavigationButton(Z)Z
move-result v$register
"""
)
}
}
//
// Toolbar.
//
val toolbarPreferences = mutableSetOf(
SwitchPreference("revanced_hide_toolbar_create_button"),
SwitchPreference("revanced_hide_toolbar_notification_button"),
SwitchPreference("revanced_hide_toolbar_search_button")
)
if (!is_20_31_or_greater) {
toolbarPreferences += SwitchPreference("revanced_wide_searchbar")
}
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
PreferenceScreenPreference(
key = "revanced_toolbar_screen",
sorting = Sorting.UNSORTED,
preferences = toolbarPreferences
)
)
hookToolbar("${EXTENSION_CLASS_DESCRIPTOR}->hideCreateButton")
hookToolbar("${EXTENSION_CLASS_DESCRIPTOR}->hideNotificationButton")
hookToolbar("${EXTENSION_CLASS_DESCRIPTOR}->hideSearchButton")
//
// Wide searchbar.
//
// YT removed the legacy text search text field all code required to use it.
// This functionality could be restored by adding a search text field to the toolbar
// with a listener that artificially clicks the toolbar search button.
if (!is_20_31_or_greater) {
// Navigate to the method that checks if the YT logo is shown beside the search bar.
val shouldShowLogoMethod = with(setWordmarkHeaderMethod) {
val invokeStaticIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC && methodReference?.returnType == "Z"
}
navigate(this).to(invokeStaticIndex).stop()
}
shouldShowLogoMethod.apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructionsAtControlFlowLabel(
index,
"""
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->enableWideSearchbar(Z)Z
move-result v$register
"""
)
}
}
// Fix missing left padding when using wide searchbar.
wideSearchbarLayoutMethod.apply {
findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/view/LayoutInflater;" && reference.name == "inflate"
}.forEach { inflateIndex ->
val register =
getInstruction<OneRegisterInstruction>(inflateIndex + 1).registerA
addInstruction(
inflateIndex + 2,
"invoke-static { v$register }, " +
"${EXTENSION_CLASS_DESCRIPTOR}->setActionBar(Landroid/view/View;)V"
)
}
}
}
}
}

View file

@ -1,150 +0,0 @@
package app.revanced.patches.youtube.layout.buttons.navigation
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_15_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/NavigationButtonsPatch;"
@Suppress("ObjectPropertyName")
val navigationButtonsPatch = bytecodePatch(
name = "Navigation buttons",
description = "Adds options to hide and change navigation buttons (such as the Shorts button).",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
navigationBarHookPatch,
versionCheckPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
),
)
apply {
addResources("youtube", "layout.buttons.navigation.navigationButtonsPatch")
val preferences = mutableSetOf(
SwitchPreference("revanced_hide_home_button"),
SwitchPreference("revanced_hide_shorts_button"),
SwitchPreference("revanced_hide_create_button"),
SwitchPreference("revanced_hide_subscriptions_button"),
SwitchPreference("revanced_hide_notifications_button"),
SwitchPreference("revanced_switch_create_with_notifications_button"),
SwitchPreference("revanced_hide_navigation_button_labels"),
)
if (is_19_25_or_greater) {
preferences += SwitchPreference("revanced_disable_translucent_navigation_bar_light")
preferences += SwitchPreference("revanced_disable_translucent_navigation_bar_dark")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_disable_translucent_status_bar"),
)
}
if (is_20_15_or_greater) {
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_navigation_bar_animations"),
)
}
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
PreferenceScreenPreference(
key = "revanced_navigation_buttons_screen",
sorting = Sorting.UNSORTED,
preferences = preferences,
),
)
// Switch create with notifications button.
addCreateButtonViewMethodMatch.method.apply {
val conditionalCheckIndex = addCreateButtonViewMethodMatch[1]
val conditionRegister =
getInstruction<OneRegisterInstruction>(conditionalCheckIndex).registerA
addInstructions(
conditionalCheckIndex,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->switchCreateWithNotificationButton()Z
move-result v$conditionRegister
""",
)
}
// Hide navigation button labels.
createPivotBarMethodMatch.let {
it.method.apply {
val setTextIndex = it[0]
val targetRegister = getInstruction<FiveRegisterInstruction>(setTextIndex).registerC
addInstruction(
setTextIndex,
"invoke-static { v$targetRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->hideNavigationButtonLabels(Landroid/widget/TextView;)V",
)
}
}
// Hook navigation button created, in order to hide them.
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
// Force on/off translucent effect on status bar and navigation buttons.
if (is_19_25_or_greater) {
translucentNavigationStatusBarFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationStatusBar(Z)Z",
)
}
translucentNavigationButtonsFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
)
}
translucentNavigationButtonsSystemFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
)
}
}
if (is_20_15_or_greater) {
animatedNavigationTabsFeatureFlagMethodMatch.let {
it.method.insertLiteralOverride(
it[0],
"$EXTENSION_CLASS_DESCRIPTOR->useAnimatedNavigationButtons(Z)Z",
)
}
}
}
}

View file

@ -4,6 +4,7 @@ import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.shared.misc.mapping.ResourceType
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.mediaRouteButtonMethod by gettingFirstMethodDeclaratively {
name("setVisibility")
@ -30,3 +31,23 @@ internal val BytecodePatchContext.inflateControlsGroupLayoutStubMethodMatch by c
method("inflate"),
)
}
internal val BytecodePatchContext.fullscreenButtonMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameterTypes("Landroid/view/View;")
returnType("V")
instructions(
ResourceType.ID("fullscreen_button"),
Opcode.CHECK_CAST()
)
}
internal val BytecodePatchContext.titleAnchorMethodMatch by composingFirstMethod {
returnType("V")
instructions(
ResourceType.ID("player_collapse_button"),
Opcode.CHECK_CAST(),
ResourceType.ID("title_anchor"),
Opcode.MOVE_RESULT_OBJECT()
)
}

View file

@ -26,7 +26,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val hidePlayerOverlayButtonsPatch = bytecodePatch(
name = "Hide player overlay buttons",
description = "Adds options to hide the player Cast, Autoplay, Captions, Previous & Next buttons, and the player " +
"control buttons background.",
"control buttons background.",
) {
dependsOn(
sharedExtensionPatch,
@ -38,10 +38,12 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -49,23 +51,26 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
addResources("youtube", "layout.buttons.overlay.hidePlayerOverlayButtonsPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_hide_player_previous_next_buttons"),
SwitchPreference("revanced_hide_cast_button"),
SwitchPreference("revanced_hide_captions_button"),
SwitchPreference("revanced_hide_autoplay_button"),
SwitchPreference("revanced_hide_captions_button"),
SwitchPreference("revanced_hide_cast_button"),
SwitchPreference("revanced_hide_collapse_button"),
SwitchPreference("revanced_hide_fullscreen_button"),
SwitchPreference("revanced_hide_player_control_buttons_background"),
SwitchPreference("revanced_hide_player_previous_next_buttons"),
)
// region Hide player next/previous button.
getLayoutConstructorMethodMatch().let {
val insertIndex = it[-1]
val viewRegister = it.method.getInstruction<FiveRegisterInstruction>(insertIndex).registerC
val viewRegister =
it.method.getInstruction<FiveRegisterInstruction>(insertIndex).registerC
it.method.addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR" +
"->hidePreviousNextButtons(Landroid/view/View;)V",
"->hidePreviousNextButtons(Landroid/view/View;)V",
)
}
@ -84,7 +89,7 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
if (is_20_28_or_greater) {
arrayOf(
castButtonPlayerFeatureFlagMethodMatch,
castButtonActionFeatureFlagMethodMatch,
castButtonActionFeatureFlagMethodMatch, // Cast button in the feed.
).forEach { match ->
match.method.insertLiteralOverride(
match[0],
@ -118,14 +123,14 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
val gotoIndex = indexOfFirstInstructionOrThrow(constIndex) {
val parameterTypes = getReference<MethodReference>()?.parameterTypes
opcode == Opcode.INVOKE_VIRTUAL &&
parameterTypes?.size == 2 &&
parameterTypes.first() == "Landroid/view/ViewStub;"
parameterTypes?.size == 2 &&
parameterTypes.first() == "Landroid/view/ViewStub;"
} + 1
addInstructionsWithLabels(
constIndex,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideAutoPlayButton()Z
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideAutoplayButton()Z
move-result v$constRegister
if-nez v$constRegister, :hidden
""",
@ -135,6 +140,54 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
// endregion
// region Hide collapse button.
titleAnchorMethodMatch.let {
it.method.apply {
val titleAnchorIndex = it[-1]
val titleAnchorRegister = getInstruction<OneRegisterInstruction>(titleAnchorIndex).registerA
addInstruction(
titleAnchorIndex + 1,
"invoke-static { v$titleAnchorRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->setTitleAnchorStartMargin(Landroid/view/View;)V"
)
val playerCollapseButtonIndex = it[1]
val playerCollapseButtonRegister = getInstruction<OneRegisterInstruction>(playerCollapseButtonIndex).registerA
addInstruction(
playerCollapseButtonIndex + 1,
"invoke-static { v$playerCollapseButtonRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->hideCollapseButton(Landroid/widget/ImageView;)V"
)
}
}
// endregion
// region Hide fullscreen button.
fullscreenButtonMethodMatch.let {
it.method.apply {
val castIndex = it[1]
val insertIndex = castIndex + 1
val insertRegister = getInstruction<OneRegisterInstruction>(castIndex).registerA
addInstructionsWithLabels(
insertIndex,
"""
invoke-static { v$insertRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->hideFullscreenButton(Landroid/widget/ImageView;)Landroid/widget/ImageView;
move-result-object v$insertRegister
if-nez v$insertRegister, :show
return-void
""",
ExternalLabel("show", getInstruction(insertIndex))
)
}
}
// endregion
// region Hide player control buttons background.
inflateControlsGroupLayoutStubMethodMatch.let {

View file

@ -7,15 +7,16 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.youtube.layout.buttons.navigation.navigationButtonsPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
@Suppress("unused")
val changeFormFactorPatch = bytecodePatch(
@ -26,15 +27,17 @@ val changeFormFactorPatch = bytecodePatch(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
navigationButtonsPatch,
navigationBarHookPatch
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -0,0 +1,74 @@
package app.revanced.patches.youtube.layout.hide.autoplaypreview
import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.getLayoutConstructorMethodMatch
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstResourceIdOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import kotlin.collections.first
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideAutoplayPreviewPatch;"
@Suppress("unused")
val hideAutoplayPreviewPatch = bytecodePatch(
name = "Hide autoplay preview",
description = "Adds an option to hide the autoplay preview at the end of videos.",
) {
dependsOn(
settingsPatch,
sharedExtensionPatch,
resourceMappingPatch
)
compatibleWith(
"com.google.android.youtube"(
"20.14.43",
"20.21.37",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
)
)
apply {
addResources("youtube", "layout.hide.autoplaypreview.hideAutoplayPreviewPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_hide_autoplay_preview")
)
getLayoutConstructorMethodMatch().method.apply {
val constIndex = indexOfFirstResourceIdOrThrow("autonav_preview_stub")
val constRegister = getInstruction<OneRegisterInstruction>(constIndex).registerA
val gotoIndex = indexOfFirstInstructionOrThrow(constIndex) {
val parameterTypes = methodReference?.parameterTypes
opcode == Opcode.INVOKE_VIRTUAL &&
parameterTypes?.size == 2 &&
parameterTypes.first() == "Landroid/view/ViewStub;"
} + 1
addInstructionsWithLabels(
constIndex,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideAutoplayPreview()Z
move-result v$constRegister
if-nez v$constRegister, :hidden
""",
ExternalLabel("hidden", getInstruction(gotoIndex)),
)
}
}
}

View file

@ -2,13 +2,9 @@ package app.revanced.patches.youtube.layout.hide.endscreencards
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal val BytecodePatchContext.layoutCircleMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
@ -33,7 +29,7 @@ internal val BytecodePatchContext.layoutIconMethodMatch by composingFirstMethod
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
)
)
literal { layoutIcon }
}
@ -55,14 +51,18 @@ internal val BytecodePatchContext.showEndscreenCardsMethod by gettingFirstMethod
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("L")
instructions(
allOf(Opcode.IPUT_OBJECT(), field { type == "Ljava/lang/String;" }),
allOf(Opcode.IGET_OBJECT(), field { type == "Ljava/lang/String;" }),
afterAtMost(7, allOf(Opcode.INVOKE_VIRTUAL(), method("ordinal"))),
5L(),
8L(),
9L()
)
custom {
immutableClassDef.methods.count() == 5 &&
containsLiteralInstruction(0) &&
containsLiteralInstruction(5) &&
containsLiteralInstruction(8) &&
indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"
} >= 0
immutableClassDef.methods.count() == 5
// 'public final' or 'final'
&& AccessFlags.FINAL.isSet(accessFlags)
}
}

View file

@ -3,6 +3,7 @@ package app.revanced.patches.youtube.layout.hide.endscreencards
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
@ -35,7 +36,7 @@ private val hideEndScreenCardsResourcePatch = resourcePatch {
addResources("youtube", "layout.hide.endscreencards.hideEndScreenCardsResourcePatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_hide_endscreen_cards"),
SwitchPreference("revanced_hide_end_screen_cards"),
)
fun idOf(name: String) = ResourceType.LAYOUT["endscreen_element_layout_$name"]
@ -62,10 +63,12 @@ val hideEndScreenCardsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -82,7 +85,7 @@ val hideEndScreenCardsPatch = bytecodePatch(
addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->hideEndScreenCardView(Landroid/view/View;)V",
"$EXTENSION_CLASS_DESCRIPTOR->hideEndScreenCardView(Landroid/view/View;)V",
)
}
}

View file

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.hide.endscreensuggestion
package app.revanced.patches.youtube.layout.hide.endscreensuggestedvideo
import app.revanced.patcher.*
import app.revanced.patcher.extensions.instructions

View file

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.hide.endscreensuggestion
package app.revanced.patches.youtube.layout.hide.endscreensuggestedvideo
import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructionsWithLabels
@ -31,15 +31,17 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
addResources("youtube", "layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch")
addResources("youtube", "layout.hide.endscreensuggestedvideo.hideEndScreenSuggestedVideoPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_end_screen_suggested_video"),
@ -49,18 +51,21 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
val endScreenMethod = navigate(it.immutableMethod).to(it[-1]).stop()
endScreenMethod.apply {
val autoNavStatusMethodName = autoNavConstructorMethod.immutableClassDef.getAutoNavStatusMethod().name
val autoNavStatusMethodName =
autoNavConstructorMethod.immutableClassDef.getAutoNavStatusMethod().name
val invokeIndex = indexOfFirstInstructionOrThrow {
val reference = methodReference
reference?.name == autoNavStatusMethodName &&
reference.returnType == "Z" &&
reference.parameterTypes.isEmpty()
reference.returnType == "Z" &&
reference.parameterTypes.isEmpty()
}
val iGetObjectIndex = indexOfFirstInstructionReversedOrThrow(invokeIndex, Opcode.IGET_OBJECT)
val iGetObjectIndex =
indexOfFirstInstructionReversedOrThrow(invokeIndex, Opcode.IGET_OBJECT)
val invokeReference = getInstruction<ReferenceInstruction>(invokeIndex).reference
val iGetObjectReference = getInstruction<ReferenceInstruction>(iGetObjectIndex).reference
val iGetObjectReference =
getInstruction<ReferenceInstruction>(iGetObjectIndex).reference
val opcodeName = getInstruction(invokeIndex).opcode.name
addInstructionsWithLabels(

View file

@ -30,15 +30,20 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
addResources("youtube", "layout.hide.fullscreenambientmode.disableFullscreenAmbientModePatch")
addResources(
"youtube",
"layout.hide.fullscreenambientmode.disableFullscreenAmbientModePatch"
)
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_disable_fullscreen_ambient_mode"),

View file

@ -1,34 +1,96 @@
package app.revanced.patches.youtube.layout.hide.general
import app.revanced.patcher.*
import app.revanced.patcher.after
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.youtube.layout.searchbar.wideSearchbarLayoutMethod
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
/**
* 20.26+
*/
internal val BytecodePatchContext.hideShowMoreButtonMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL, AccessFlags.SYNTHETIC)
internal val BytecodePatchContext.hideShowMoreButtonSetViewMethodMatch by composingFirstMethod {
returnType("V")
parameterTypes("L", "Ljava/lang/Object;")
var methodDefiningClass = ""
custom {
methodDefiningClass = definingClass
true
}
instructions(
ResourceType.LAYOUT("expand_button_down"),
method { toString() == "Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;" },
after(Opcode.MOVE_RESULT_OBJECT()),
ResourceType.ID("link_text_start"),
allOf(
Opcode.IPUT_OBJECT(),
field { type == "Landroid/widget/TextView;" && definingClass == methodDefiningClass }),
ResourceType.ID("expand_button_container"),
allOf(
Opcode.IPUT_OBJECT(),
field { type == "Landroid/view/View;" && definingClass == methodDefiningClass })
)
}
internal val BytecodePatchContext.hideShowMoreLegacyButtonMethodMatch by composingFirstMethod {
context(_: BytecodePatchContext)
internal fun ClassDef.getHideShowMoreButtonGetParentViewMethod() =
firstImmutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
parameterTypes()
}
context(_: BytecodePatchContext)
internal fun ClassDef.getHideShowMoreButtonMethod() = firstMethodDeclaratively {
returnType("V")
parameterTypes("L", "Ljava/lang/Object;")
instructions(
allOf(
Opcode.INVOKE_VIRTUAL(),
method {
toString() == "Landroid/view/View;->setContentDescription(Ljava/lang/CharSequence;)V"
}
)
)
}
/**
* 20.21+
*/
internal val BytecodePatchContext.hideSubscribedChannelsBarConstructorMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
ResourceType.LAYOUT("expand_button_down"),
method { toString() == "Landroid/view/View;->inflate(Landroid/content/Context;ILandroid/view/ViewGroup;)Landroid/view/View;" },
Opcode.MOVE_RESULT_OBJECT(),
ResourceType.ID("parent_container"),
afterAtMost(3, Opcode.MOVE_RESULT_OBJECT()),
afterAtMost(
5,
allOf(Opcode.NEW_INSTANCE(), type($$"Landroid/widget/LinearLayout$LayoutParams;"))
)
)
custom { immutableClassDef.anyField { type == "Landroid/support/v7/widget/RecyclerView;" } }
}
/**
* ~ 20.21
*/
internal val BytecodePatchContext.hideSubscribedChannelsBarConstructorLegacyMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
ResourceType.ID("parent_container"),
afterAtMost(3, Opcode.MOVE_RESULT_OBJECT()),
afterAtMost(
5,
allOf(Opcode.NEW_INSTANCE(), type($$"Landroid/widget/LinearLayout$LayoutParams;"))
)
)
}
internal val ClassDef.hideSubscribedChannelsBarLandscapeMethodMatch by ClassDefComposing.composingFirstMethod {
returnType("V")
parameterTypes()
instructions(
ResourceType.DIMEN("parent_view_width_in_wide_mode"),
allOf(Opcode.INVOKE_VIRTUAL(), method("getDimensionPixelSize")),
after(Opcode.MOVE_RESULT())
)
}
@ -146,3 +208,124 @@ internal val BytecodePatchContext.hideViewCountMethodMatch by composingFirstMeth
Opcode.RETURN_OBJECT,
)
}
internal val BytecodePatchContext.searchBoxTypingStringMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("L")
instructions(
allOf(Opcode.IGET_OBJECT(), field { type == "Ljava/util/Collection;" }),
afterAtMost(5, method { toString() == "Ljava/util/ArrayList;-><init>(Ljava/util/Collection;)V" }),
allOf(Opcode.IGET_OBJECT(), field { type == "Ljava/lang/String;" }),
afterAtMost(5, method { toString() == "Ljava/lang/String;->isEmpty()Z" }),
ResourceType.DIMEN("suggestion_category_divider_height")
)
}
internal val BytecodePatchContext.searchSuggestionEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively(
"\u2026 "
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
returnType("V")
}
internal val ClassDef.searchSuggestionEndpointMethodMatch by ClassDefComposing.composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")
parameterTypes()
var methodDefiningClass = ""
custom {
methodDefiningClass = definingClass
true
}
instructions(
allOf(
Opcode.IGET_OBJECT(),
field { definingClass == methodDefiningClass && type == "Ljava/lang/String;" }),
allOf(
Opcode.INVOKE_STATIC(),
method { toString() == "Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z" }),
)
}
internal val BytecodePatchContext.latestVideosContentPillMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("L", "Z")
instructions(
ResourceType.LAYOUT("content_pill"),
method {
toString() == "Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;"
},
after(Opcode.MOVE_RESULT_OBJECT())
)
}
internal val BytecodePatchContext.latestVideosBarMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("L", "Z")
instructions(
ResourceType.LAYOUT("bar"),
method {
toString() == "Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;"
},
after(Opcode.MOVE_RESULT_OBJECT())
)
}
internal val BytecodePatchContext.bottomSheetMenuItemBuilderMethodMatch by composingFirstMethod {
returnType("L")
parameterTypes("L")
instructions(
allOf(
Opcode.INVOKE_STATIC(),
method {
returnType == "Ljava/lang/CharSequence;" &&
parameterTypes.size == 1 && parameterTypes[0].startsWith("L")
}
),
after(Opcode.MOVE_RESULT_OBJECT()),
"Text missing for BottomSheetMenuItem."()
)
}
internal val BytecodePatchContext.contextualMenuItemBuilderMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL, AccessFlags.SYNTHETIC)
returnType("V")
parameterTypes("L", "L")
instructions(
allOf(Opcode.CHECK_CAST(), type("Landroid/widget/TextView;")),
afterAtMost(
5,
method { toString() == "Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V" }
),
ResourceType.DIMEN("poster_art_width_default"),
)
}
internal val BytecodePatchContext.channelTabBuilderMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Landroid/view/View;")
parameterTypes(
"Ljava/lang/CharSequence;",
"Ljava/lang/CharSequence;",
"Z",
"L"
)
}
internal val BytecodePatchContext.channelTabRendererMethod by gettingFirstMethodDeclaratively(
"TabRenderer.content contains SectionListRenderer but the tab does not have a section list controller."
) {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes(
"L",
"Ljava/util/List;",
"I"
)
}

View file

@ -1,6 +1,8 @@
package app.revanced.patches.youtube.layout.hide.general
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.CompositeMatch
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.*
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.resourcePatch
@ -9,9 +11,11 @@ import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.*
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_20_26_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_21_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
@ -19,12 +23,18 @@ import app.revanced.util.findFreeRegister
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method
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
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
internal var albumCardId = -1L
private set
@ -71,6 +81,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
hideLayoutComponentsResourcePatch,
navigationBarHookPatch,
versionCheckPatch,
engagementPanelHookPatch,
resourceMappingPatch,
),
filterClasses = setOf(
@ -82,15 +93,23 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
),
compatibleWithPackages = arrayOf(
"com.google.android.youtube" to setOf(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
),
) {
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
PreferenceScreen.ADS.addPreferences(
// Uses horizontal shelf and a buffer, which requires managing in a single place in the code
// to ensure the generic "hide horizontal shelves" doesn't hide when it should show.
SwitchPreference("revanced_hide_creator_store_shelf")
)
PreferenceScreen.PLAYER.addPreferences(
PreferenceScreenPreference(
key = "revanced_hide_description_components_screen",
@ -99,15 +118,22 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
SwitchPreference("revanced_hide_ask_section"),
SwitchPreference("revanced_hide_attributes_section"),
SwitchPreference("revanced_hide_chapters_section"),
SwitchPreference("revanced_hide_course_progress_section"),
SwitchPreference("revanced_hide_explore_section"),
SwitchPreference("revanced_hide_explore_course_section"),
SwitchPreference("revanced_hide_explore_podcast_section"),
SwitchPreference("revanced_hide_featured_links_section"),
SwitchPreference("revanced_hide_featured_places_section"),
SwitchPreference("revanced_hide_featured_videos_section"),
SwitchPreference("revanced_hide_info_cards_section"),
SwitchPreference("revanced_hide_gaming_section"),
SwitchPreference("revanced_hide_how_this_was_made_section"),
SwitchPreference("revanced_hide_hype_points"),
SwitchPreference("revanced_hide_info_cards_section"),
SwitchPreference("revanced_hide_key_concepts_section"),
SwitchPreference("revanced_hide_podcast_section"),
SwitchPreference("revanced_hide_music_section"),
SwitchPreference("revanced_hide_subscribe_button"),
SwitchPreference("revanced_hide_transcript_section"),
SwitchPreference("revanced_hide_quizzes_section")
),
),
PreferenceScreenPreference(
@ -118,13 +144,14 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
SwitchPreference("revanced_hide_comments_channel_guidelines"),
SwitchPreference("revanced_hide_comments_by_members_header"),
SwitchPreference("revanced_hide_comments_section"),
SwitchPreference("revanced_hide_comments_section_in_home_feed"),
SwitchPreference("revanced_hide_comments_community_guidelines"),
SwitchPreference("revanced_hide_comments_create_a_short_button"),
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
SwitchPreference("revanced_hide_comments_preview_comment"),
SwitchPreference("revanced_hide_comments_thanks_button"),
),
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
sorting = Sorting.UNSORTED,
),
SwitchPreference("revanced_hide_channel_bar"),
SwitchPreference("revanced_hide_channel_watermark"),
@ -132,29 +159,34 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
SwitchPreference("revanced_hide_emergency_box"),
SwitchPreference("revanced_hide_info_panels"),
SwitchPreference("revanced_hide_join_membership_button"),
SwitchPreference("revanced_hide_live_chat_replay_button"),
SwitchPreference("revanced_hide_medical_panels"),
SwitchPreference("revanced_hide_quick_actions"),
SwitchPreference("revanced_hide_related_videos"),
SwitchPreference("revanced_hide_subscribers_community_guidelines"),
SwitchPreference("revanced_hide_timed_reactions"),
SwitchPreference("revanced_hide_video_title")
)
PreferenceScreen.FEED.addPreferences(
PreferenceScreenPreference(
key = "revanced_hide_keyword_content_screen",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_hide_keyword_content_home"),
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
SwitchPreference("revanced_hide_keyword_content_search"),
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
TextPreference(
"revanced_hide_keyword_content_phrases",
inputType = InputType.TEXT_MULTI_LINE
),
NonInteractivePreference(
key = "revanced_hide_keyword_content_about",
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference",
),
NonInteractivePreference(
key = "revanced_hide_keyword_content_about_whole_words",
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
tag = "app.revanced.extension.youtube.settings.preference.HTMLPreference",
),
),
),
@ -170,6 +202,18 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
PreferenceScreenPreference(
key = "revanced_channel_screen",
preferences = setOf(
PreferenceCategory(
titleKey = null,
sorting = Sorting.UNSORTED,
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
preferences = setOf(
SwitchPreference("revanced_hide_channel_tab"),
TextPreference(
"revanced_hide_channel_tab_filter_strings",
inputType = InputType.TEXT_MULTI_LINE
),
)
),
SwitchPreference("revanced_hide_community_button"),
SwitchPreference("revanced_hide_for_you_shelf"),
SwitchPreference("revanced_hide_join_button"),
@ -185,6 +229,18 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
SwitchPreference("revanced_hide_community_posts"),
SwitchPreference("revanced_hide_compact_banner"),
SwitchPreference("revanced_hide_expandable_card"),
PreferenceCategory(
titleKey = null,
sorting = Sorting.UNSORTED,
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
preferences = setOf(
SwitchPreference("revanced_hide_feed_flyout_menu"),
TextPreference(
"revanced_hide_feed_flyout_menu_filter_strings",
inputType = InputType.TEXT_MULTI_LINE
),
)
),
SwitchPreference("revanced_hide_floating_microphone_button"),
SwitchPreference(
key = "revanced_hide_horizontal_shelves",
@ -192,6 +248,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
),
SwitchPreference("revanced_hide_image_shelf"),
SwitchPreference("revanced_hide_latest_posts"),
SwitchPreference("revanced_hide_latest_videos_button"),
SwitchPreference("revanced_hide_mix_playlists"),
SwitchPreference("revanced_hide_movies_section"),
SwitchPreference("revanced_hide_notify_me_button"),
@ -206,7 +263,13 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
SwitchPreference("revanced_hide_doodles"),
)
// region Mix playlists
if (is_20_21_or_greater) {
PreferenceScreen.FEED.addPreferences(
SwitchPreference("revanced_hide_you_may_like_section")
)
}
// region Hide mix playlists
parseElementFromBufferMethodMatch.let {
it.method.apply {
@ -214,12 +277,18 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
val insertIndex = startIndex + 1
val byteArrayParameter = "p3"
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
val conversionContextRegister =
getInstruction<TwoRegisterInstruction>(startIndex).registerA
val returnEmptyComponentInstruction =
instructions.last { it.opcode == Opcode.INVOKE_STATIC }
val returnEmptyComponentRegister =
(returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
val freeRegister =
findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
findFreeRegister(
insertIndex,
conversionContextRegister,
returnEmptyComponentRegister
)
addInstructionsWithLabels(
insertIndex,
@ -239,7 +308,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
// endregion
// region Watermark (legacy code for old versions of YouTube)
// region Hide watermark (legacy code for old versions of YouTube)
playerOverlayMethod.immutableClassDef.getShowWatermarkMethod().apply {
val index = implementation!!.instructions.size - 5
@ -256,25 +325,94 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
// endregion
// region Show more button
// region Hide Show more button
(if (is_20_26_or_greater) hideShowMoreButtonMethodMatch else hideShowMoreLegacyButtonMethodMatch).let {
val (textViewField, buttonContainerField) = hideShowMoreButtonSetViewMethodMatch.let {
val textViewIndex = it[1]
val buttonContainerIndex = it[3]
Pair(
it.method.getInstruction<ReferenceInstruction>(textViewIndex).reference,
it.method.getInstruction<ReferenceInstruction>(buttonContainerIndex).reference
)
}
val parentViewMethod = hideShowMoreButtonSetViewMethodMatch.immutableClassDef
.getHideShowMoreButtonGetParentViewMethod()
hideShowMoreButtonSetViewMethodMatch.immutableClassDef.getHideShowMoreButtonMethod().apply {
val helperMethod = ImmutableMethod(
definingClass,
"patch_hideShowMoreButton",
listOf(),
"V",
AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(7),
).toMutable().apply {
addInstructions(
0,
"""
move-object/from16 v0, p0
invoke-virtual { v0 }, $parentViewMethod
move-result-object v1
iget-object v2, v0, $buttonContainerField
iget-object v3, v0, $textViewField
invoke-static { v1, v2, v3 }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideShowMoreButton(Landroid/view/View;Landroid/view/View;Landroid/widget/TextView;)V
return-void
"""
)
}.also(classDef.methods::add)
findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index ->
addInstruction(index, "invoke-direct/range { p0 .. p0 }, $helperMethod")
}
}
// endregion
// region Hide Subscribed channels bar
// Tablet
val methodMatch = if (is_20_21_or_greater)
hideSubscribedChannelsBarConstructorMethodMatch
else hideSubscribedChannelsBarConstructorLegacyMethodMatch
methodMatch.let {
it.method.apply {
val moveRegisterIndex = it[-1]
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
val index = it[1]
val register = getInstruction<OneRegisterInstruction>(index).registerA
val insertIndex = moveRegisterIndex + 1
addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideShowMoreButton(Landroid/view/View;)V",
index + 1,
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideSubscribedChannelsBar(Landroid/view/View;)V",
)
}
}
// Phone (landscape mode)
methodMatch.immutableClassDef.hideSubscribedChannelsBarLandscapeMethodMatch.let {
it.method.apply {
val index = it[-1]
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideSubscribedChannelsBar(I)I
move-result v$register
"""
)
}
}
// endregion
// region crowdfunding box
// region Hide Crowdfunding box
crowdfundingBoxMethodMatch.let {
it.method.apply {
val insertIndex = it[-1]
@ -283,14 +421,14 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
addInstruction(
insertIndex,
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideCrowdfundingBox(Landroid/view/View;)V",
"->hideCrowdfundingBox(Landroid/view/View;)V",
)
}
}
// endregion
// region hide album cards
// region Hide Album cards
albumCardsMethodMatch.let {
it.method.apply {
@ -301,14 +439,14 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
addInstruction(
insertIndex,
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideAlbumCard(Landroid/view/View;)V",
"->hideAlbumCard(Landroid/view/View;)V",
)
}
}
// endregion
// region hide floating microphone
// region Hide Floating microphone
showFloatingMicrophoneButtonMethodMatch.let {
it.method.apply {
@ -327,7 +465,27 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
// endregion
// region 'Yoodles'
// region Hide latest videos button
listOf(
latestVideosContentPillMethodMatch,
latestVideosBarMethodMatch,
).forEach { match ->
match.method.apply {
val moveIndex = match[-1]
val viewRegister = getInstruction<OneRegisterInstruction>(moveIndex).registerA
addInstruction(
moveIndex + 1,
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideLatestVideosButton(Landroid/view/View;)V"
)
}
}
// endregion
// region Hide 'Yoodles'
yoodlesImageViewMethod.apply {
findInstructionIndicesReversedOrThrow {
@ -339,14 +497,14 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
replaceInstruction(
insertIndex,
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V",
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V",
)
}
}
// endregion
// region hide view count
// region Hide view count
hideViewCountMethodMatch.method.apply {
val startIndex = hideViewCountMethodMatch[0]
@ -356,10 +514,10 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_STATIC &&
reference?.definingClass == "Landroid/util/TypedValue;" &&
reference.returnType == "F" &&
reference.name == "applyDimension" &&
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
reference?.definingClass == "Landroid/util/TypedValue;" &&
reference.returnType == "F" &&
reference.name == "applyDimension" &&
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
}
// A float value is passed which is used to determine subtitle text size.
@ -378,7 +536,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
// endregion
// region hide filter bar
// region Hide filter bar
/**
* Patch a [Method] with a given [instructions].
@ -416,6 +574,179 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
relatedChipCloudMethodMatch.patch<OneRegisterInstruction>(1) { register ->
"invoke-static { v$register }, " +
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
}
// endregion
// region Hide You may like section
if (is_20_21_or_greater) {
val searchSuggestionEndpointField =
searchSuggestionEndpointConstructorMethod.immutableClassDef
.searchSuggestionEndpointMethodMatch.let {
it.method.getInstruction(it[0]).fieldReference!!
}
val searchSuggestionEndpointClass = searchSuggestionEndpointField.definingClass
searchBoxTypingStringMethodMatch.let {
it.method.apply {
// A collection of search suggestions.
// This includes trending search (also known as 'You may like' section)
// and your search history.
val searchSuggestionCollectionField = getInstruction(it[0]).fieldReference!!
val typedStringField = getInstruction(it[2]).fieldReference!!
val helperMethod = ImmutableMethod(
definingClass,
"patch_setSearchSuggestions",
listOf(
ImmutableMethodParameter(
parameterTypes.first().toString(),
null,
null
)
),
"V",
AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
annotations,
null,
MutableMethodImplementation(7),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
move-object/from16 v0, p1
iget-object v1, v0, $typedStringField
# Check if the setting is enabled and if the typed string is empty.
invoke-static { v1 }, ${LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR}->hideYouMayLikeSection(Ljava/lang/String;)Z
move-result v1
# If the setting is disabled or the typed string is not empty, do nothing.
if-eqz v1, :ignore
## Get a collection of search suggestions.
iget-object v1, v0, $searchSuggestionCollectionField
# Iterate through the collection and check if the search suggestion is the search history.
invoke-interface { v1 }, Ljava/util/Collection;->iterator()Ljava/util/Iterator;
move-result-object v2
:loop
invoke-interface { v2 }, Ljava/util/Iterator;->hasNext()Z
move-result v3
if-eqz v3, :exit
invoke-interface { v2 }, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v3
instance-of v4, v3, $searchSuggestionEndpointClass
if-eqz v4, :loop
check-cast v3, $searchSuggestionEndpointClass
# Each search suggestion has a command endpoint.
# If the search suggestion is the search history, the command includes the keyword '/delete'.
iget-object v4, v3, $searchSuggestionEndpointField
invoke-static { v3, v4 }, ${LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR}->isSearchHistory(Ljava/lang/Object;Ljava/lang/String;)Z
move-result v3
# If this search suggestion is the search history, do nothing.
if-nez v3, :loop
# If this search suggestion is not the search history, remove it from the search suggestions collection.
invoke-interface { v2 }, Ljava/util/Iterator;->remove()V
goto :loop
# Save the updated collection to a field.
:exit
iput-object v1, v0, $searchSuggestionCollectionField
:ignore
return-void
"""
)
}.also(it.classDef.methods::add)
addInstruction(
0,
"invoke-direct/range { p0 .. p1 }, $helperMethod"
)
}
}
}
// endregion
// region Hide flyout menu items
bottomSheetMenuItemBuilderMethodMatch.let {
it.method.apply {
val index = it[1]
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, ${LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR}->hideFlyoutMenu(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$register
"""
)
}
}
contextualMenuItemBuilderMethodMatch.let {
it.method.apply {
val index = it[1]
val targetInstruction = getInstruction<FiveRegisterInstruction>(index)
addInstruction(
index + 1,
"invoke-static { v${targetInstruction.registerC}, v${targetInstruction.registerD} }, " +
"${LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR}->hideFlyoutMenu(Landroid/widget/TextView;Ljava/lang/CharSequence;)V"
)
}
}
// endregion
// region Hide channel tab
channelTabRendererMethod.apply {
val iteratorIndex = indexOfFirstInstructionReversedOrThrow {
methodReference?.name == "hasNext"
}
val iteratorRegister = getInstruction<FiveRegisterInstruction>(iteratorIndex).registerC
val targetIndex = indexOfFirstInstructionReversedOrThrow {
val reference = methodReference
opcode == Opcode.INVOKE_INTERFACE &&
reference?.returnType == channelTabBuilderMethod.returnType &&
reference.parameterTypes == channelTabBuilderMethod.parameterTypes
}
val objectIndex = indexOfFirstInstructionReversedOrThrow(
targetIndex,
Opcode.IGET_OBJECT
)
val objectInstruction = getInstruction<TwoRegisterInstruction>(objectIndex)
val objectReference = getInstruction<ReferenceInstruction>(objectIndex).reference
addInstructionsWithLabels(
objectIndex + 1,
"""
invoke-static { v${objectInstruction.registerA} }, ${LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR}->hideChannelTab(Ljava/lang/String;)Z
move-result v${objectInstruction.registerA}
if-eqz v${objectInstruction.registerA}, :ignore
invoke-interface { v$iteratorRegister }, Ljava/util/Iterator;->remove()V
goto :next_iterator
:ignore
iget-object v${objectInstruction.registerA}, v${objectInstruction.registerB}, $objectReference
""",
ExternalLabel("next_iterator", getInstruction(iteratorIndex))
)
}
// endregion
}

View file

@ -47,10 +47,12 @@ val hideInfoCardsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -61,21 +63,21 @@ val hideInfoCardsPatch = bytecodePatch(
SwitchPreference("revanced_hide_info_cards"),
)
// Edit: This old non litho code may be obsolete and no longer used by any supported versions.
// Edit: This old non-litho code may be obsolete and no longer used by any supported versions.
infocardsIncognitoParentMethod.immutableClassDef.getInfocardsIncognitoMethod().apply {
val invokeInstructionIndex = implementation!!.instructions.indexOfFirst {
it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal &&
((it as ReferenceInstruction).reference.toString() == "Landroid/view/View;->setVisibility(I)V")
((it as ReferenceInstruction).reference.toString() == "Landroid/view/View;->setVisibility(I)V")
}
addInstruction(
invokeInstructionIndex,
"invoke-static {v${getInstruction<FiveRegisterInstruction>(invokeInstructionIndex).registerC}}," +
" Lapp/revanced/extension/youtube/patches/HideInfoCardsPatch;->hideInfoCardsIncognito(Landroid/view/View;)V",
" Lapp/revanced/extension/youtube/patches/HideInfoCardsPatch;->hideInfoCardsIncognito(Landroid/view/View;)V",
)
}
// Edit: This old non litho code may be obsolete and no longer used by any supported versions.
// Edit: This old non-litho code may be obsolete and no longer used by any supported versions.
infocardsMethodCallMethodMatch.let {
val invokeInterfaceIndex = it[-1]
it.method.apply {
@ -97,7 +99,8 @@ val hideInfoCardsPatch = bytecodePatch(
}
// Info cards can also appear as Litho components.
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/litho/HideInfoCardsFilter;"
val filterClassDescriptor =
"Lapp/revanced/extension/youtube/patches/litho/HideInfoCardsFilter;"
addLithoFilter(filterClassDescriptor)
}
}

View file

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.hide.player.flyoutmenupanel
package app.revanced.patches.youtube.layout.hide.player.flyoutmenu
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
@ -25,15 +25,18 @@ val hidePlayerFlyoutMenuItemsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter;"
val filterClassDescriptor =
"Lapp/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter;"
addResources("youtube", "layout.hide.player.flyoutmenupanel.hidePlayerFlyoutMenuPatch")
@ -42,24 +45,24 @@ val hidePlayerFlyoutMenuItemsPatch = bytecodePatch(
key = "revanced_hide_player_flyout",
preferences = setOf(
SwitchPreference("revanced_hide_player_flyout_captions"),
SwitchPreference("revanced_hide_player_flyout_additional_settings"),
SwitchPreference("revanced_hide_player_flyout_loop_video"),
SwitchPreference("revanced_hide_player_flyout_ambient_mode"),
SwitchPreference("revanced_hide_player_flyout_stable_volume"),
SwitchPreference("revanced_hide_player_flyout_listen_with_youtube_music"),
SwitchPreference("revanced_hide_player_flyout_help"),
SwitchPreference("revanced_hide_player_flyout_speed"),
SwitchPreference("revanced_hide_player_flyout_lock_screen"),
SwitchPreference(
key = "revanced_hide_player_flyout_audio_track",
tag = "app.revanced.extension.youtube.settings.preference.HideAudioFlyoutMenuPreference",
tag = "app.revanced.extension.youtube.settings.preference.HideAudioFlyoutMenuPreference"
),
SwitchPreference("revanced_hide_player_flyout_watch_in_vr"),
SwitchPreference("revanced_hide_player_flyout_sleep_timer"),
SwitchPreference("revanced_hide_player_flyout_video_quality"),
SwitchPreference("revanced_hide_player_flyout_video_quality_footer"),
SwitchPreference("revanced_hide_player_flyout_additional_settings"),
SwitchPreference("revanced_hide_player_flyout_ambient_mode"),
SwitchPreference("revanced_hide_player_flyout_stable_volume"),
SwitchPreference("revanced_hide_player_flyout_loop_video"),
SwitchPreference("revanced_hide_player_flyout_sleep_timer"),
SwitchPreference("revanced_hide_player_flyout_watch_in_vr"),
),
),
)
)
addLithoFilter(filterClassDescriptor)

View file

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.panels.popup
package app.revanced.patches.youtube.layout.hide.player.popup
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.patch.bytecodePatch
@ -8,8 +8,10 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.getEngagementPanelControllerMethodMatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/DisablePlayerPopupPanelsPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisablePlayerPopupPanelsPatch;"
@Suppress("unused")
val disablePlayerPopupPanelsPatch = bytecodePatch(
@ -24,10 +26,12 @@ val disablePlayerPopupPanelsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -38,7 +42,7 @@ val disablePlayerPopupPanelsPatch = bytecodePatch(
SwitchPreference("revanced_hide_player_popup_panels"),
)
engagementPanelControllerMethod.addInstructionsWithLabels(
getEngagementPanelControllerMethodMatch().method.addInstructionsWithLabels(
0,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disablePlayerPopupPanels()Z

View file

@ -30,10 +30,12 @@ val hideRelatedVideoOverlayPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -30,10 +30,12 @@ val disableRollingNumberAnimationsPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -7,6 +7,21 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
internal val BytecodePatchContext.componentContextParserMethod by gettingFirstImmutableMethodDeclaratively {
returnType("L")
instructions(
"Failed to parse Element proto."(),
"Cannot read theme key from model."()
)
}
context(_: BytecodePatchContext)
internal fun ClassDef.getTreeNodeResultListMethod() = firstMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returnType("Ljava/util/List;")
instructions(allOf(Opcode.INVOKE_STATIC(), method("nCopies")))
}
internal val BytecodePatchContext.shortsBottomBarContainerMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
@ -19,9 +34,6 @@ internal val BytecodePatchContext.shortsBottomBarContainerMethodMatch by composi
)
}
/**
* 19.41 to 20.44.
*/
context(_: BytecodePatchContext)
internal fun ClassDef.getRenderBottomNavigationBarMethodMatch() = firstMethodDeclaratively {
@ -34,10 +46,10 @@ internal fun ClassDef.getRenderBottomNavigationBarMethodMatch() = firstMethodDec
after(Opcode.IF_EQZ()),
after(Opcode.INVOKE_INTERFACE()),
Opcode.MONITOR_EXIT(),
after(Opcode.RETURN_VOID()),
after(Opcode.MOVE_EXCEPTION()),
after(Opcode.MONITOR_EXIT()),
after(Opcode.THROW()),
Opcode.RETURN_VOID(),
Opcode.MOVE_EXCEPTION(),
Opcode.MONITOR_EXIT(),
Opcode.THROW()
)
}
@ -59,8 +71,7 @@ internal val BytecodePatchContext.legacyRenderBottomNavigationBarLegacyParentMet
}
/**
* Identical to [legacyRenderBottomNavigationBarLegacyParentMethod]
* except this has an extra parameter.
* 19.41 - 20.44
*/
internal val BytecodePatchContext.renderBottomNavigationBarLegacy1941ParentMethod by gettingFirstImmutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
@ -78,6 +89,9 @@ internal val BytecodePatchContext.renderBottomNavigationBarLegacy1941ParentMetho
)
}
/**
* 20.45+
*/
internal val BytecodePatchContext.renderBottomNavigationBarParentMethod by gettingFirstImmutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("[Ljava/lang/Class;")

View file

@ -12,11 +12,13 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.shared.conversionContextToStringMethod
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
@ -25,8 +27,6 @@ import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.*
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import java.util.logging.Logger
internal val hideShortsAppShortcutOption = booleanOption(
name = "Hide Shorts app shortcut",
@ -54,82 +54,65 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
addResources("youtube", "layout.hide.shorts.hideShortsComponentsResourcePatch")
val preferences = mutableSetOf(
// Shorts player components.
// Ideally each group should be ordered similar to how they appear in the UI
// Vertical row of buttons on right side of the screen.
SwitchPreference("revanced_hide_shorts_like_fountain"),
SwitchPreference("revanced_hide_shorts_like_button"),
SwitchPreference("revanced_hide_shorts_dislike_button"),
)
if (is_20_22_or_greater) {
// FIXME: The buffer is very different for 20.22+ and these current cannot be hidden.
Logger.getLogger(this::class.java.name).warning(
"\n!!!" +
"\n!!! Shorts action buttons currently cannot be set hidden when patching 20.22+" +
"\n!!! Patch 20.21.37 or lower if you want to hide Shorts action buttons" +
"\n!!!",
)
} else {
preferences.addAll(
listOf(
SwitchPreference("revanced_hide_shorts_comments_button"),
SwitchPreference("revanced_hide_shorts_share_button"),
SwitchPreference("revanced_hide_shorts_remix_button"),
SwitchPreference("revanced_hide_shorts_sound_button"),
),
)
}
preferences.addAll(
listOf(
// Upper and middle area of the player.
SwitchPreference("revanced_hide_shorts_join_button"),
SwitchPreference("revanced_hide_shorts_subscribe_button"),
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
// Suggested actions.
SwitchPreference("revanced_hide_shorts_preview_comment"),
SwitchPreference("revanced_hide_shorts_save_sound_button"),
SwitchPreference("revanced_hide_shorts_use_sound_button"),
SwitchPreference("revanced_hide_shorts_use_template_button"),
SwitchPreference("revanced_hide_shorts_upcoming_button"),
SwitchPreference("revanced_hide_shorts_effect_button"),
SwitchPreference("revanced_hide_shorts_green_screen_button"),
SwitchPreference("revanced_hide_shorts_hashtag_button"),
SwitchPreference("revanced_hide_shorts_live_preview"),
SwitchPreference("revanced_hide_shorts_new_posts_button"),
SwitchPreference("revanced_hide_shorts_shop_button"),
SwitchPreference("revanced_hide_shorts_tagged_products"),
SwitchPreference("revanced_hide_shorts_search_suggestions"),
SwitchPreference("revanced_hide_shorts_super_thanks_button"),
SwitchPreference("revanced_hide_shorts_stickers"),
// Bottom of the screen.
SwitchPreference("revanced_hide_shorts_auto_dubbed_label"),
SwitchPreference("revanced_hide_shorts_location_label"),
SwitchPreference("revanced_hide_shorts_channel_bar"),
SwitchPreference("revanced_hide_shorts_info_panel"),
SwitchPreference("revanced_hide_shorts_full_video_link_label"),
SwitchPreference("revanced_hide_shorts_video_title"),
SwitchPreference("revanced_hide_shorts_sound_metadata_label"),
SwitchPreference("revanced_hide_shorts_navigation_bar"),
),
)
PreferenceScreen.SHORTS.addPreferences(
SwitchPreference("revanced_hide_shorts_channel"),
SwitchPreference("revanced_hide_shorts_home"),
SwitchPreference("revanced_hide_shorts_search"),
SwitchPreference("revanced_hide_shorts_subscriptions"),
SwitchPreference("revanced_hide_shorts_video_description"),
SwitchPreference("revanced_hide_shorts_history"),
PreferenceScreenPreference(
key = "revanced_shorts_player_screen",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = preferences,
),
preferences = setOf(
// Shorts player components.
// Ideally each group should be ordered similar to how they appear in the UI
// Vertical row of buttons on right side of the screen.
// Like fountain may no longer be used by YT anymore.
//SwitchPreference("revanced_hide_shorts_like_fountain"),
SwitchPreference("revanced_hide_shorts_like_button"),
SwitchPreference("revanced_hide_shorts_dislike_button"),
SwitchPreference("revanced_hide_shorts_comments_button"),
SwitchPreference("revanced_hide_shorts_share_button"),
SwitchPreference("revanced_hide_shorts_remix_button"),
SwitchPreference("revanced_hide_shorts_sound_button"),
// Upper and middle area of the player.
SwitchPreference("revanced_hide_shorts_join_button"),
SwitchPreference("revanced_hide_shorts_subscribe_button"),
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
// Suggested actions.
SwitchPreference("revanced_hide_shorts_preview_comment"),
SwitchPreference("revanced_hide_shorts_save_sound_button"),
SwitchPreference("revanced_hide_shorts_use_sound_button"),
SwitchPreference("revanced_hide_shorts_use_template_button"),
SwitchPreference("revanced_hide_shorts_upcoming_button"),
SwitchPreference("revanced_hide_shorts_effect_button"),
SwitchPreference("revanced_hide_shorts_green_screen_button"),
SwitchPreference("revanced_hide_shorts_hashtag_button"),
SwitchPreference("revanced_hide_shorts_live_preview"),
SwitchPreference("revanced_hide_shorts_new_posts_button"),
SwitchPreference("revanced_hide_shorts_shop_button"),
SwitchPreference("revanced_hide_shorts_tagged_products"),
SwitchPreference("revanced_hide_shorts_search_suggestions"),
SwitchPreference("revanced_hide_shorts_super_thanks_button"),
SwitchPreference("revanced_hide_shorts_stickers"),
// Bottom of the screen.
SwitchPreference("revanced_hide_shorts_ai_button"),
SwitchPreference("revanced_hide_shorts_auto_dubbed_label"),
SwitchPreference("revanced_hide_shorts_location_label"),
SwitchPreference("revanced_hide_shorts_channel_bar"),
SwitchPreference("revanced_hide_shorts_info_panel"),
SwitchPreference("revanced_hide_shorts_full_video_link_label"),
SwitchPreference("revanced_hide_shorts_video_title"),
SwitchPreference("revanced_hide_shorts_sound_metadata_label"),
SwitchPreference("revanced_hide_shorts_navigation_bar"),
),
)
)
// Verify the file has the expected node, even if the patch option is off.
@ -157,28 +140,33 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
}
}
private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/litho/ShortsFilter;"
private const val FILTER_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/litho/ShortsFilter;"
@Suppress("unused")
val hideShortsComponentsPatch = bytecodePatch(
name = "Hide Shorts components",
description = "Adds options to hide components related to Shorts.",
description = "Adds options to hide components related to Shorts. " +
"Patching version 20.21.37 or lower can hide more Shorts player button types."
) {
dependsOn(
sharedExtensionPatch,
lithoFilterPatch,
hideShortsComponentsResourcePatch,
resourceMappingPatch,
engagementPanelHookPatch,
navigationBarHookPatch,
versionCheckPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
// 20.22+ does not yet support hiding Shorts action buttons.
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -188,26 +176,66 @@ val hideShortsComponentsPatch = bytecodePatch(
apply {
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
// region Hide sound button.
val id = ResourceType.DIMEN["reel_player_right_pivot_v2_size"]
forEachInstructionAsSequence({ _, method, instruction, index ->
if (instruction.wideLiteral != id) return@forEachInstructionAsSequence null
if (!is_21_05_or_greater) {
forEachInstructionAsSequence({ _, method, instruction, index ->
if (instruction.wideLiteral != id) return@forEachInstructionAsSequence null
val targetIndex = method.indexOfFirstInstructionOrThrow(index) {
methodReference?.name == "getDimensionPixelSize"
} + 1
val targetIndex = method.indexOfFirstInstructionOrThrow(index) {
methodReference?.name == "getDimensionPixelSize"
} + 1
val sizeRegister = method.getInstruction<OneRegisterInstruction>(targetIndex).registerA
val sizeRegister = method.getInstruction<OneRegisterInstruction>(targetIndex).registerA
return@forEachInstructionAsSequence targetIndex to sizeRegister
}) { method, (targetIndex, sizeRegister) ->
firstMethod(method).addInstructions(
targetIndex + 1,
"""
return@forEachInstructionAsSequence targetIndex to sizeRegister
}) { method, (targetIndex, sizeRegister) ->
firstMethod(method).addInstructions(
targetIndex + 1,
"""
invoke-static { v$sizeRegister }, $FILTER_CLASS_DESCRIPTOR->getSoundButtonSize(I)I
move-result v$sizeRegister
""",
)
)
}
}
// endregion
// region Hide action buttons.
if (is_20_22_or_greater) {
componentContextParserMethod.immutableClassDef.getTreeNodeResultListMethod().apply {
val conversionContextPathBuilderField =
conversionContextToStringMethod.immutableClassDef
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
val insertIndex = implementation!!.instructions.lastIndex
val listRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
val registerProvider = getFreeRegisterProvider(insertIndex, 2)
val freeRegister = registerProvider.getFreeRegister()
val pathRegister = registerProvider.getFreeRegister()
addInstructionsAtControlFlowLabel(
insertIndex,
"""
move-object/from16 v$freeRegister, p2
# In YouTube 20.41 field is the abstract superclass.
# Verify it's the expected subclass just in case.
instance-of v$pathRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef}
if-eqz v$pathRegister, :ignore
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
invoke-static { v$pathRegister, v$listRegister }, ${FILTER_CLASS_DESCRIPTOR}->hideActionButtons(Ljava/lang/StringBuilder;Ljava/util/List;)V
:ignore
nop
"""
)
}
}
// endregion
@ -262,7 +290,7 @@ val hideShortsComponentsPatch = bytecodePatch(
// region Disable experimental Shorts flags.
// Flags might be present in earlier targets, but they are not found in 19.47.53.
// If these flags are forced on, the experimental layout is still not used and
// If these flags are forced on, the experimental layout is still not used, and
// it appears the features requires additional server side data to fully use.
if (is_20_07_or_greater) {
// Experimental Shorts player uses Android native buttons and not Litho,
@ -274,7 +302,7 @@ val hideShortsComponentsPatch = bytecodePatch(
shortsExperimentalPlayerFeatureFlagMethod.returnLate(false)
// Experimental UI renderer must also be disabled since it requires the
// experimental Shorts player. If this is enabled but Shorts player
// experimental Shorts player. If this is enabled but Shorts player
// is disabled then the app crashes when the Shorts player is opened.
renderNextUIFeatureFlagMethod.returnLate(false)
}

View file

@ -11,7 +11,7 @@ import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableSignInToTvPopupPatch;"
"Lapp/revanced/extension/youtube/patches/DisableSignInToTVPopupPatch;"
@Suppress("unused")
val disableSignInToTVPopupPatch = bytecodePatch(
@ -27,24 +27,26 @@ val disableSignInToTVPopupPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
apply {
addResources("youtube", "layout.hide.signintotv.disableSignInToTvPopupPatch")
addResources("youtube", "layout.hide.signintotv.disableSignInToTVPopupPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_disable_signin_to_tv_popup"),
SwitchPreference("revanced_disable_sign_in_to_tv_popup"),
)
signInToTvPopupMethod.addInstructionsWithLabels(
signInToTVPopupMethod.addInstructionsWithLabels(
0,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSignInToTvPopup()Z
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSignInToTVPopup()Z
move-result v0
if-eqz v0, :allow_sign_in_popup
const/4 v0, 0x0

View file

@ -7,7 +7,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patches.shared.misc.mapping.ResourceType
internal val BytecodePatchContext.signInToTvPopupMethod by gettingFirstMethodDeclaratively {
internal val BytecodePatchContext.signInToTVPopupMethod by gettingFirstMethodDeclaratively {
returnType("Z")
parameterTypes("Ljava/lang/String;", "Z", "L")
instructions(ResourceType.STRING("mdx_seamless_tv_sign_in_drawer_fragment_title"))

View file

@ -9,7 +9,8 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/HideTimestampPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideTimestampPatch;"
@Suppress("unused")
val hideTimestampPatch = bytecodePatch(
@ -24,10 +25,12 @@ val hideTimestampPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -12,8 +12,10 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L
// In later targets this feature flag does nothing and is dead code.
internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L
internal const val MINIPLAYER_MODERN_TYPE_1_FEATURE_KEY = 45623000L
internal const val MINIPLAYER_MODERN_TYPE_2_FEATURE_KEY = 45623273L
internal const val MINIPLAYER_MODERN_TYPE_3_FEATURE_KEY = 45623076L
internal const val MINIPLAYER_MODERN_TYPE_4_FEATURE_KEY = 45674402L
internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L
internal const val MINIPLAYER_DRAG_DROP_FEATURE_KEY = 45628752L
internal const val MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY = 45658112L
@ -21,11 +23,13 @@ internal const val MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY = 45652224L
internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L
internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L
internal const val MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY = 45644360L
// In later targets this feature flag does nothing and is dead code.
internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L
internal val BytecodePatchContext.miniplayerModernConstructorMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
45623000L(), // Magic number found in the constructor.
MINIPLAYER_MODERN_TYPE_1_FEATURE_KEY(),
)
}
@ -148,8 +152,14 @@ internal val BytecodePatchContext.miniplayerMinimumSizeMethodMatch by composingF
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
instructions(
ResourceType.DIMEN("miniplayer_max_size"),
192L(), // Default miniplayer width constant.
128L(), // Default miniplayer height constant.
anyOf( // Default miniplayer width constant.
192L(),
192.0f.toRawBits().toLong()(), // 21.03+
),
anyOf( // Default miniplayer height constant.
128L(),
128.0f.toRawBits().toLong()(), // 21.03+
)
)
}

View file

@ -53,13 +53,15 @@ private val miniplayerResourcePatch = resourcePatch {
// Only required for exactly 19.16
if (!is_19_17_or_greater) {
ytOutlinePictureInPictureWhite24 = ResourceType.DRAWABLE["yt_outline_picture_in_picture_white_24"]
ytOutlinePictureInPictureWhite24 =
ResourceType.DRAWABLE["yt_outline_picture_in_picture_white_24"]
ytOutlineXWhite24 = ResourceType.DRAWABLE["yt_outline_x_white_24"]
}
}
}
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/MiniplayerPatch;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/MiniplayerPatch;"
@Suppress("unused")
val miniplayerPatch = bytecodePatch(
@ -75,10 +77,12 @@ val miniplayerPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -143,7 +147,10 @@ val miniplayerPatch = bytecodePatch(
}
if (is_19_26_or_greater) {
preferences += TextPreference("revanced_miniplayer_width_dip", inputType = InputType.NUMBER)
preferences += TextPreference(
"revanced_miniplayer_width_dip",
inputType = InputType.NUMBER
)
}
preferences += TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER)
@ -167,7 +174,8 @@ val miniplayerPatch = bytecodePatch(
)
}
fun Method.findReturnIndicesReversed() = findInstructionIndicesReversedOrThrow(Opcode.RETURN)
fun Method.findReturnIndicesReversed() =
findInstructionIndicesReversedOrThrow(Opcode.RETURN)
/**
* Adds an override to force legacy tablet miniplayer to be used or not used.
@ -227,11 +235,12 @@ val miniplayerPatch = bytecodePatch(
// Parts of the YT code is removed in 20.37+ and the legacy player no longer works.
if (!is_20_37_or_greater) {
miniplayerDimensionsCalculatorParentMethod.immutableClassDef.getMiniplayerOverrideNoContextMethod().apply {
findReturnIndicesReversed().forEach { index ->
insertLegacyTabletMiniplayerOverride(index)
miniplayerDimensionsCalculatorParentMethod.immutableClassDef.getMiniplayerOverrideNoContextMethod()
.apply {
findReturnIndicesReversed().forEach { index ->
insertLegacyTabletMiniplayerOverride(index)
}
}
}
// endregion
@ -265,7 +274,11 @@ val miniplayerPatch = bytecodePatch(
insertModernMiniplayerTypeOverride(iPutIndex)
} else {
findReturnIndicesReversed().forEach { index -> insertModernMiniplayerOverride(index) }
findReturnIndicesReversed().forEach { index ->
insertModernMiniplayerOverride(
index
)
}
}
}
}
@ -355,17 +368,23 @@ val miniplayerPatch = bytecodePatch(
// YT fixed this mistake in 19.17.
// Fix this, by swapping the drawable resource values with each other.
if (!is_19_17_or_greater) {
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernExpandCloseDrawablesMethod().apply {
listOf(
ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24,
ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24,
).forEach { (originalResource, replacementResource) ->
val imageResourceIndex = indexOfFirstLiteralInstructionOrThrow(originalResource)
val register = getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernExpandCloseDrawablesMethod()
.apply {
listOf(
ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24,
ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24,
).forEach { (originalResource, replacementResource) ->
val imageResourceIndex =
indexOfFirstLiteralInstructionOrThrow(originalResource)
val register =
getInstruction<OneRegisterInstruction>(imageResourceIndex).registerA
replaceInstruction(imageResourceIndex, "const v$register, $replacementResource")
replaceInstruction(
imageResourceIndex,
"const v$register, $replacementResource"
)
}
}
}
}
// endregion
@ -377,7 +396,7 @@ val miniplayerPatch = bytecodePatch(
findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_INTERFACE &&
reference?.returnType == "Z" && reference.parameterTypes.isEmpty()
reference?.returnType == "Z" && reference.parameterTypes.isEmpty()
}.forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
@ -417,11 +436,12 @@ val miniplayerPatch = bytecodePatch(
}
}
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod().addInstruction(
0,
"invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"hideMiniplayerSubTexts(Landroid/view/View;)V",
)
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod()
.addInstruction(
0,
"invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"hideMiniplayerSubTexts(Landroid/view/View;)V",
)
// Modern 2 has a broken overlay subtitle view that is always present.
// Modern 2 uses the same overlay controls as the regular video player,

View file

@ -1,12 +0,0 @@
package app.revanced.patches.youtube.layout.panels.popup
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
internal val BytecodePatchContext.engagementPanelControllerMethod by gettingFirstMethodDeclaratively(
"EngagementPanelController: cannot show EngagementPanel before EngagementPanelController.init() has been called.",
"[EngagementPanel] Cannot show EngagementPanel before EngagementPanelController.init() has been called.",
) {
returnType("L")
}

View file

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
@ -9,10 +10,9 @@ import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.information.videoEndMethod
import app.revanced.patches.youtube.video.information.playerStatusMethod
import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
@Suppress("unused")
@ -22,10 +22,12 @@ val exitFullscreenPatch = bytecodePatch(
) {
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -51,13 +53,15 @@ val exitFullscreenPatch = bytecodePatch(
ListPreference("revanced_exit_fullscreen"),
)
videoEndMethod.apply {
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_VOID)
playerStatusMethod.apply {
// +1 to ensure inserted after the loop logic added by the "Loop video" patch.
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.SGET_OBJECT) + 1
addInstructionsAtControlFlowLabel(
addInstruction(
insertIndex,
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached()V",
)
"invoke-static/range { p1 .. p1 }, " +
"$EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached(Ljava/lang/Enum;)V",
)
}
}
}

View file

@ -36,7 +36,7 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
// Remove A/B feature call that forces what this patch already does.
// Cannot use the A/B flag to accomplish the same goal because 19.50+
// Shorts fullscreen regular player does not use fullscreen
// if the player is minimized and it must be forced using other conditional check.
// if the player is minimized, and it must be forced using other conditional check.
it.method.insertLiteralOverride(
it[-1],
false,

View file

@ -21,12 +21,13 @@ val openVideosFullscreenPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"19.43.41",
"19.47.53",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)

View file

@ -28,10 +28,12 @@ val customPlayerOverlayOpacityPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.43.41",
"20.14.43",
"20.21.37",
"20.31.40",
"20.26.46",
"20.31.42",
"20.37.48",
"20.40.45"
),
)
@ -45,12 +47,13 @@ val customPlayerOverlayOpacityPatch = bytecodePatch(
createPlayerOverviewMethodMatch.let {
it.method.apply {
val viewRegisterIndex = it[-1]
val viewRegister = getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA
val viewRegister =
getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA
addInstruction(
viewRegisterIndex + 1,
"invoke-static { v$viewRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->changeOpacity(Landroid/widget/ImageView;)V",
"$EXTENSION_CLASS_DESCRIPTOR->changeOpacity(Landroid/widget/ImageView;)V",
)
}
}

Some files were not shown because too many files have changed in this diff Show more