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 - SponsorBlock): Skip segments when casting #3331

Merged
merged 11 commits into from
Jul 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import com.android.tools.smali.dexlib2.util.MethodUtil
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference

@Patch(
description = "Hooks YouTube to get information about the current playing video.",
Expand All @@ -32,6 +35,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
object VideoInformationPatch : BytecodePatch(
setOf(
PlayerInitFingerprint,
MdxPlayerDirectorFingerprint,
CreateVideoPlayerSeekbarFingerprint,
PlayerControllerSetTimeReferenceFingerprint,
OnPlaybackSpeedItemClickFingerprint
Expand All @@ -42,6 +46,10 @@ object VideoInformationPatch : BytecodePatch(
private lateinit var playerInitMethod: MutableMethod
private var playerInitInsertIndex = 4

private lateinit var mdxInitMethod: MutableMethod
private var mdxInitInsertIndex = 5
private var mdxInitInsertRegister = 8

private lateinit var timeMethod: MutableMethod
private var timeInitInsertIndex = 2

Expand All @@ -55,6 +63,7 @@ object VideoInformationPatch : BytecodePatch(
internal lateinit var setPlaybackSpeedMethodReference: String

override fun execute(context: BytecodeContext) {

with(PlayerInitFingerprint.result!!) {
playerInitMethod = mutableClass.methods.first { MethodUtil.isConstructor(it) }

Expand All @@ -65,34 +74,34 @@ object VideoInformationPatch : BytecodePatch(
val seekFingerprintResultMethod = SeekFingerprint.also { it.resolve(context, classDef) }.result!!.method

// create helper method
val seekHelperMethod = ImmutableMethod(
seekFingerprintResultMethod.definingClass,
"seekTo",
listOf(ImmutableMethodParameter("J", null, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null, null,
MutableMethodImplementation(4)
).toMutable()

// get enum type for the seek helper method
val seekSourceEnumType = seekFingerprintResultMethod.parameterTypes[1].toString()

// insert helper method instructions
seekHelperMethod.addInstructions(
0,
"""
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${seekFingerprintResultMethod.definingClass}->${seekFingerprintResultMethod.name}(J$seekSourceEnumType)Z
move-result p1
return p1
"""
)
val seekHelperMethod = generateSeekMethodHelper(seekFingerprintResultMethod)

// add the seekTo method to the class for the integrations to call
mutableClass.methods.add(seekHelperMethod)
}

with(MdxPlayerDirectorFingerprint.result!!) {
mdxInitMethod = mutableClass.methods.first { MethodUtil.isConstructor(it) }

// find the location of the first invoke-direct call and extract the register storing the 'this' object reference
mdxInitInsertIndex = mdxInitMethod.implementation!!.instructions.indexOfFirst {
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
it.opcode == Opcode.INVOKE_DIRECT && ((it as Instruction35c).reference as MethodReference).name == "<init>"
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
} + 1
mdxInitInsertRegister = mdxInitMethod.getInstruction<Instruction35c>(mdxInitInsertIndex - 1).registerC
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved

// hook the MDX director for use through integrations
onCreateHookMdx(INTEGRATIONS_CLASS_DESCRIPTOR, "initializeMdx")

// MDX seek method
val mdxSeekFingerprintResultMethod = MdxSeekFingerprint.also { it.resolve(context, classDef) }.result!!.method
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved

// create helper method
val mdxSeekHelperMethod = generateSeekMethodHelper(mdxSeekFingerprintResultMethod)

// add the seekTo method to the class for the integrations to call
mutableClass.methods.add(mdxSeekHelperMethod)
}

with(CreateVideoPlayerSeekbarFingerprint.result!!) {
val videoLengthMethodResult = VideoLengthFingerprint.also { it.resolve(context, classDef) }.result!!

Expand Down Expand Up @@ -158,6 +167,35 @@ object VideoInformationPatch : BytecodePatch(
userSelectedPlaybackSpeedHook(INTEGRATIONS_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
}

private fun generateSeekMethodHelper(seekMethod: Method): MutableMethod {

// create helper method
val mdxSeekHelperMethod = ImmutableMethod(
seekMethod.definingClass,
"seekTo",
listOf(ImmutableMethodParameter("J", null, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null, null,
MutableMethodImplementation(4)
).toMutable()

// get enum type for the seek helper method
val seekSourceEnumType = seekMethod.parameterTypes[1].toString()

// insert helper method instructions
mdxSeekHelperMethod.addInstructions(
0,
"""
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${seekMethod.definingClass}->${seekMethod.name}(J$seekSourceEnumType)Z
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
move-result p1
return p1
"""
)
return mdxSeekHelperMethod
}

private fun MutableMethod.insert(insertIndex: Int, register: String, descriptor: String) =
addInstruction(insertIndex, "invoke-static { $register }, $descriptor")

Expand All @@ -180,6 +218,19 @@ object VideoInformationPatch : BytecodePatch(
"$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V"
)

/**
* Hook the MDX player director. Called when playing videos while casting to a big screen device.
*
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun onCreateHookMdx(targetMethodClass: String, targetMethodName: String) =
mdxInitMethod.insert(
mdxInitInsertIndex++,
"v$mdxInitInsertRegister",
"$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V"
)

/**
* Hook the video time.
* The hook is usually called once per second.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.revanced.patches.youtube.video.information.fingerprints


import app.revanced.patcher.fingerprint.MethodFingerprint

internal object MdxPlayerDirectorFingerprint : MethodFingerprint(
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
strings = listOf("MdxDirector setVideoStage ad should be null when videoStage is not an Ad state ")
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.revanced.patches.youtube.video.information.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

internal object MdxSeekFingerprint : MethodFingerprint(
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z",
parameters = listOf("J", "L"),
customFingerprint = { methodDef, _ ->
methodDef.implementation!!.instructions.count() == 3
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
}
)
Loading