diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index 28be7e5f9..dc0fd4955 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -51,9 +51,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( override fun execute(context: BytecodeContext) { // region Inject newVideoLoaded event handler to update dislikes when a new video is loaded. - // This patch needs a few adjustments and lots of testing before it can change to the new video id hook. - // There's a few corner cases and some weirdness when loading new videos (specifically with detecting shorts). - VideoIdPatch.legacyInjectCall("$INTEGRATIONS_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") + VideoIdPatch.hookVideoId("$INTEGRATIONS_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt index 5ae4631b7..ddc1e9c15 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt @@ -96,11 +96,8 @@ object SponsorBlockBytecodePatch : BytecodePatch( /* * Set current video id. - * - * The new video id hook seems to work without issues, - * but it's easier to keep using this hook as it's well tested and has no known problems. */ - VideoIdPatch.legacyInjectCallBackgroundPlay("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") + VideoIdPatch.hookBackgroundPlayVideoId("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") /* * Seekbar drawing diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt index 55e6014ba..2f594c7aa 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt @@ -88,7 +88,9 @@ object SpoofSignaturePatch : BytecodePatch( ) // Hook the player parameters. - PlayerResponseMethodHookPatch.injectProtoBufferHook("$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;)Ljava/lang/String;") + PlayerResponseMethodHookPatch + PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter( + "$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;)Ljava/lang/String;" + ) // Force the seekbar time and chapters to always show up. // This is used only if the storyboard spec fetch fails, or when viewing paid videos. 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 4e0a6970e..2aee32fe0 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 @@ -109,9 +109,13 @@ object VideoInformationPatch : BytecodePatch( } /* - * Inject call for video id + * Inject call for video ids */ - VideoIdPatch.injectCall("$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V") + val videoIdMethodDescriptor = "$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V" + VideoIdPatch.hookVideoId(videoIdMethodDescriptor) + VideoIdPatch.hookBackgroundPlayVideoId(videoIdMethodDescriptor) + VideoIdPatch.hookPlayerResponseVideoId( + "$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(Ljava/lang/String;)V") /* * Set the video time method diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt index 53670c37b..a08531bb1 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt @@ -7,66 +7,58 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patches.youtube.misc.fix.playback.SpoofSignaturePatch import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.video.playerresponse.fingerprint.PlayerParameterBuilderFingerprint -import app.revanced.patches.youtube.video.videoid.VideoIdPatch +import java.io.Closeable @Patch( dependencies = [IntegrationsPatch::class], ) -object PlayerResponseMethodHookPatch : BytecodePatch( - setOf( - PlayerParameterBuilderFingerprint, - ) -) { - private const val playerResponseVideoIdParameter = 1 - private const val playerResponseProtoBufferParameter = 3 - /** - * Insert index when adding a video id hook. - */ - private var playerResponseVideoIdInsertIndex = 0 - /** - * Insert index when adding a proto buffer override. - * Must be after all video id hooks in the same method. - */ - private var playerResponseProtoBufferInsertIndex = 0 +object PlayerResponseMethodHookPatch : + BytecodePatch(setOf(PlayerParameterBuilderFingerprint)), + Closeable, + MutableSet by mutableSetOf() { + private const val VIDEO_ID_PARAMETER = 1 + private const val PROTO_BUFFER_PARAMETER_PARAMETER = 3 + private lateinit var playerResponseMethod: MutableMethod override fun execute(context: BytecodeContext) { - - // Hook player parameter. - PlayerParameterBuilderFingerprint.result?.let { - playerResponseMethod = it.mutableMethod - } ?: throw PlayerParameterBuilderFingerprint.exception + playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod + ?: throw PlayerParameterBuilderFingerprint.exception } - /** - * Modify the player parameter proto buffer value. - * Used exclusively by [SpoofSignaturePatch]. - */ - fun injectProtoBufferHook(methodDescriptor: String) { - playerResponseMethod.addInstructions( - playerResponseProtoBufferInsertIndex, + override fun close() { + fun hookVideoId(hook: Hook) = playerResponseMethod.addInstruction( + 0, "invoke-static {p$VIDEO_ID_PARAMETER}, $hook" + ) + + fun hookProtoBufferParameter(hook: Hook) = playerResponseMethod.addInstructions( + 0, """ - invoke-static {p$playerResponseProtoBufferParameter}, $methodDescriptor - move-result-object p$playerResponseProtoBufferParameter + invoke-static {p$PROTO_BUFFER_PARAMETER_PARAMETER}, $hook + move-result-object p$PROTO_BUFFER_PARAMETER_PARAMETER """ ) - playerResponseProtoBufferInsertIndex += 2 + + // Reverse the order in order to preserve insertion order of the hooks. + val beforeVideoIdHooks = filterIsInstance().asReversed() + val videoIdHooks = filterIsInstance().asReversed() + val afterVideoIdHooks = filterIsInstance().asReversed() + + // Add the hooks in this specific order as they insert instructions at the beginning of the method. + afterVideoIdHooks.forEach(::hookProtoBufferParameter) + videoIdHooks.forEach(::hookVideoId) + beforeVideoIdHooks.forEach(::hookProtoBufferParameter) } - /** - * Used by [VideoIdPatch]. - */ - internal fun injectVideoIdHook(methodDescriptor: String) { - playerResponseMethod.addInstruction( - // Keep injection calls in the order they're added, - // and all video id hooks run before proto buffer hooks. - playerResponseVideoIdInsertIndex++, - "invoke-static {p$playerResponseVideoIdParameter}, $methodDescriptor" - ) - playerResponseProtoBufferInsertIndex++ + internal abstract class Hook(private val methodDescriptor: String) { + internal class VideoId(methodDescriptor: String) : Hook(methodDescriptor) + + internal class ProtoBufferParameter(methodDescriptor: String) : Hook(methodDescriptor) + internal class ProtoBufferParameterBeforeVideoId(methodDescriptor: String) : Hook(methodDescriptor) + + override fun toString() = methodDescriptor } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt index eccc47753..57df0c97f 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt @@ -26,8 +26,8 @@ object VideoIdPatch : BytecodePatch( ) ) { private var videoIdRegister = 0 - private var insertIndex = 0 - private lateinit var insertMethod: MutableMethod + private var videoIdInsertIndex = 0 + private lateinit var videoIdMethod: MutableMethod private var backgroundPlaybackVideoIdRegister = 0 private var backgroundPlaybackInsertIndex = 0 @@ -52,8 +52,8 @@ object VideoIdPatch : BytecodePatch( } ?: throw VideoIdFingerprint.exception VideoIdFingerprint.setFields { method, index, register -> - insertMethod = method - insertIndex = index + videoIdMethod = method + videoIdInsertIndex = index videoIdRegister = register } @@ -65,21 +65,7 @@ object VideoIdPatch : BytecodePatch( } /** - * Adds an invoke-static instruction, called with the new id when the video changes. - * - * Called as soon as the player response is parsed, and called before many other hooks are - * updated such as [PlayerTypeHookPatch]. - * - * Supports all videos and functions in all situations. - * - * Be aware, this can be called multiple times for the same video id. - * - * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;` - */ - fun injectCall(methodDescriptor: String) = PlayerResponseMethodHookPatch.injectVideoIdHook(methodDescriptor) - - /** - * Adds an invoke-static instruction, called with the new id when the video changes. + * Hooks the new video id when the video changes. * * Supports all videos (regular videos and Shorts). * @@ -89,10 +75,10 @@ object VideoIdPatch : BytecodePatch( * * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;` */ - fun legacyInjectCall( + fun hookVideoId( methodDescriptor: String - ) = insertMethod.addInstruction( - insertIndex++, + ) = videoIdMethod.addInstruction( + videoIdInsertIndex++, "invoke-static {v$videoIdRegister}, $methodDescriptor" ) @@ -106,11 +92,37 @@ object VideoIdPatch : BytecodePatch( * * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;` */ - fun legacyInjectCallBackgroundPlay( + fun hookBackgroundPlayVideoId( methodDescriptor: String ) = backgroundPlaybackMethod.addInstruction( backgroundPlaybackInsertIndex++, // move-result-object offset "invoke-static {v$backgroundPlaybackVideoIdRegister}, $methodDescriptor" ) + + /** + * Hooks the video id of every video when loaded. + * Supports all videos and functions in all situations. + * + * Hook is always called off the main thread. + * + * This hook is called as soon as the player response is parsed, + * and called before many other hooks are updated such as [PlayerTypeHookPatch]. + * + * Note: The video id returned here may not be the current video that's being played. + * It's common for multiple Shorts to load at once in preparation + * for the user swiping to the next Short. + * + * For most use cases, you probably want to use + * [hookVideoId] or [hookBackgroundPlayVideoId] instead. + * + * Be aware, this can be called multiple times for the same video id. + * + * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;` + */ + fun hookPlayerResponseVideoId(methodDescriptor: String) { + PlayerResponseMethodHookPatch + PlayerResponseMethodHookPatch.Hook.VideoId( + methodDescriptor + ) + } }