From 16ba530024e1a6f7abdcc9600e49d461e6b09378 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Thu, 2 May 2024 03:32:50 +0900 Subject: [PATCH] feat(YouTube/Overlay buttons): restore `Tap and hold to toggle pause after repeat states` features --- .../OverlayButtonsBytecodePatch.kt | 99 ++++++++++++++++++- .../PlayerButtonConstructorFingerprint.kt | 12 +++ .../utils/resourceid/SharedResourceIdPatch.kt | 2 + .../information/VideoInformationPatch.kt | 13 --- .../youtube/settings/host/values/strings.xml | 6 +- 5 files changed, 113 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/fingerprints/PlayerButtonConstructorFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch.kt index baafd5088..e068bb512 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch.kt @@ -5,32 +5,59 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.player.overlaybuttons.fingerprints.OfflineVideoEndpointFingerprint import app.revanced.patches.youtube.player.overlaybuttons.fingerprints.PiPPlaybackFingerprint +import app.revanced.patches.youtube.player.overlaybuttons.fingerprints.PlayerButtonConstructorFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH import app.revanced.patches.youtube.utils.integrations.Constants.PATCH_STATUS_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.mainactivity.MainActivityResolvePatch +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch +import app.revanced.patches.youtube.video.information.VideoInformationPatch +import app.revanced.util.addFieldAndInstructions +import app.revanced.util.getReference +import app.revanced.util.getTargetIndexWithReference +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -@Patch(dependencies = [MainActivityResolvePatch::class]) +@Patch( + dependencies = [ + MainActivityResolvePatch::class, + SharedResourceIdPatch::class, + VideoInformationPatch::class + ] +) object OverlayButtonsBytecodePatch : BytecodePatch( setOf( OfflineVideoEndpointFingerprint, - PiPPlaybackFingerprint + PiPPlaybackFingerprint, + PlayerButtonConstructorFingerprint ) ) { - private const val INTEGRATIONS_CLASS_DESCRIPTOR = + private const val INTEGRATIONS_ALWAYS_REPEAT_CLASS_DESCRIPTOR = + "$UTILS_PATH/AlwaysRepeatPatch;" + + private const val INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR = "$INTEGRATIONS_PATH/utils/VideoUtils;" override fun execute(context: BytecodeContext) { + + // region patch for hook download button + OfflineVideoEndpointFingerprint.resultOrThrow().mutableMethod.apply { addInstructionsWithLabels( 0, """ - invoke-static/range {p3 .. p3}, $INTEGRATIONS_CLASS_DESCRIPTOR->inAppDownloadButtonOnClick(Ljava/lang/String;)Z + invoke-static/range {p3 .. p3}, $INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR->inAppDownloadButtonOnClick(Ljava/lang/String;)Z move-result v0 if-eqz v0, :show_native_downloader return-void @@ -45,13 +72,75 @@ object OverlayButtonsBytecodePatch : BytecodePatch( addInstructions( insertIndex, """ - invoke-static {v$insertRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->getExternalDownloaderLaunchedState(Z)Z + invoke-static {v$insertRegister}, $INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR->getExternalDownloaderLaunchedState(Z)Z move-result v$insertRegister """ ) } } + // endregion + + // region patch for always repeat and pause + + PlayerButtonConstructorFingerprint.resultOrThrow().mutableMethod.apply { + val registerResolver = implementation!!.registerCount - parameters.size - 1 + 6 // p6 + + val invokerObjectIndex = indexOfFirstInstruction { + opcode == Opcode.IPUT_OBJECT + && getReference()?.definingClass == definingClass + && (this as TwoRegisterInstruction).registerA == registerResolver + } + val invokerObjectReference = getInstruction(invokerObjectIndex).reference + + val onClickListenerReferenceIndex = getTargetIndexWithReference("(Ljava/lang/Object;I[B)V") + val onClickListenerReference = getInstruction(onClickListenerReferenceIndex).reference + val onClickListenerClass = context.findClass((onClickListenerReference as MethodReference).definingClass)!!.mutableClass + + var invokeInterfaceReference = "" + onClickListenerClass.methods.find { method -> method.name == "onClick" } + ?.apply { + val invokeInterfaceIndex = getTargetIndexWithReference(invokerObjectReference.toString()) + 1 + if (getInstruction(invokeInterfaceIndex).opcode != Opcode.INVOKE_INTERFACE) + throw PatchException("Opcode does not match") + invokeInterfaceReference = getInstruction(invokeInterfaceIndex).reference.toString() + } ?: throw PatchException("Could not find onClick method") + + val alwaysRepeatMutableClass = context.findClass(INTEGRATIONS_ALWAYS_REPEAT_CLASS_DESCRIPTOR)!!.mutableClass + + val smaliInstructions = + """ + if-eqz v0, :ignore + iget-object v1, v0, $invokerObjectReference + if-eqz v1, :ignore + invoke-interface {v1}, $invokeInterfaceReference + :ignore + return-void + """ + + alwaysRepeatMutableClass.addFieldAndInstructions( + context, + "pauseVideo", + "pauseButtonClass", + definingClass, + smaliInstructions, + true + ) + } + + VideoInformationPatch.videoEndMethod.apply { + addInstructionsWithLabels( + 0, """ + invoke-static {}, $INTEGRATIONS_ALWAYS_REPEAT_CLASS_DESCRIPTOR->alwaysRepeat()Z + move-result v0 + if-eqz v0, :end + return-void + """, ExternalLabel("end", getInstruction(0)) + ) + } + + // endregion + context.updatePatchStatus(PATCH_STATUS_CLASS_DESCRIPTOR, "OverlayButtons") } diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/fingerprints/PlayerButtonConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/fingerprints/PlayerButtonConstructorFingerprint.kt new file mode 100644 index 000000000..ae5c16b7b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/fingerprints/PlayerButtonConstructorFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.player.overlaybuttons.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.SingleLoopEduSnackBarText +import app.revanced.util.fingerprint.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object PlayerButtonConstructorFingerprint : LiteralValueFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + literalSupplier = { SingleLoopEduSnackBarText } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index b63570438..afc9e9b15 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -85,6 +85,7 @@ object SharedResourceIdPatch : ResourcePatch() { var ScrimOverlay = -1L var Scrubbing = -1L var SeekUndoEduOverlayStub = -1L + var SingleLoopEduSnackBarText = -1L var SlidingDialogAnimation = -1L var SubtitleMenuSettingsFooterInfo = -1L var SuggestedAction = -1L @@ -172,6 +173,7 @@ object SharedResourceIdPatch : ResourcePatch() { ScrimOverlay = getId(ID, "scrim_overlay") Scrubbing = getId(DIMEN, "vertical_touch_offset_to_enter_fine_scrubbing") SeekUndoEduOverlayStub = getId(ID, "seek_undo_edu_overlay_stub") + SingleLoopEduSnackBarText = getId(STRING, "single_loop_edu_snackbar_text") SlidingDialogAnimation = getId(STYLE, "SlidingDialogAnimation") SubtitleMenuSettingsFooterInfo = getId(STRING, "subtitle_menu_settings_footer_info") SuggestedAction = getId(LAYOUT, "suggested_action") diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt index f8e5ca802..065e4dd90 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt @@ -2,7 +2,6 @@ package app.revanced.patches.youtube.video.information import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.or @@ -12,7 +11,6 @@ import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.toInstructions import app.revanced.patches.youtube.utils.fingerprints.OrganicPlaybackContextModelFingerprint import app.revanced.patches.youtube.utils.fingerprints.VideoEndFingerprint @@ -178,17 +176,6 @@ object VideoInformationPatch : BytecodePatch( ) videoEndMethod = getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex + 1) - - videoEndMethod.apply { - addInstructionsWithLabels( - 0, """ - invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->videoEnded()Z - move-result v0 - if-eqz v0, :end - return-void - """, ExternalLabel("end", getInstruction(0)) - ) - } } } diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index 93c4c1799..55f79eb7d 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -9,6 +9,7 @@ Refresh and restart Normal + Ads @@ -762,7 +763,8 @@ Limitation: Video title disappears when clicked." Overlay buttons Show always repeat button - Tap to toggle always repeat states. + "Tap to toggle always repeat states. +Tap and hold to toggle pause after repeat states." Show copy video URL button "Tap to copy video URL. Tap and hold to copy video URL with timestamp." @@ -775,6 +777,8 @@ Tap and hold to copy video timestamp." "Tap to open speed dialog. Tap and hold to set playback speed to 1.0x." Playback speed reseted (1.0x). + Tap and hold to change button state. + External downloader package name Package name of your installed external downloader app, such as NewPipe or YTDLnis. External downloader