Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(YouTube - Video Id): Fix video id not showing the currently playing video #3038

Merged
merged 8 commits into from
Sep 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.ProtoBufferParameterBeforeVideoId(
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
"$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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlayerResponseMethodHookPatch.Hook> 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<Hook.ProtoBufferParameterBeforeVideoId>().asReversed()
val videoIdHooks = filterIsInstance<Hook.VideoId>().asReversed()
val protoBufferParameterHooks = filterIsInstance<Hook.ProtoBufferParameter>().asReversed()

// Add the hooks in this specific order as they insert instructions at the beginning of the method.
protoBufferParameterHooks.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
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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).
*
Expand All @@ -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"
)

Expand All @@ -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
)
}
}