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 @@ -14,6 +14,9 @@ import app.revanced.patches.youtube.video.information.fingerprints.*
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.patches.youtube.video.videoid.VideoIdPatch
import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
Expand All @@ -24,6 +27,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.FiveRegisterInstruction
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 +38,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
object VideoInformationPatch : BytecodePatch(
setOf(
PlayerInitFingerprint,
MdxPlayerDirectorSetVideoStageFingerprint,
CreateVideoPlayerSeekbarFingerprint,
PlayerControllerSetTimeReferenceFingerprint,
OnPlaybackSpeedItemClickFingerprint
Expand All @@ -42,57 +49,65 @@ object VideoInformationPatch : BytecodePatch(
private lateinit var playerInitMethod: MutableMethod
private var playerInitInsertIndex = 4

private lateinit var mdxInitMethod: MutableMethod
private var mdxInitInsertIndex = -1
private var mdxInitInsertRegister = -1

private lateinit var timeMethod: MutableMethod
private var timeInitInsertIndex = 2

private lateinit var speedSelectionInsertMethod: MutableMethod
private var speedSelectionInsertIndex = 0
private var speedSelectionValueRegister = 0
private var speedSelectionInsertIndex = -1
private var speedSelectionValueRegister = -1

// Used by other patches.
internal lateinit var setPlaybackSpeedContainerClassFieldReference: String
internal lateinit var setPlaybackSpeedClassFieldReference: String
internal lateinit var setPlaybackSpeedMethodReference: String

override fun execute(context: BytecodeContext) {
with(PlayerInitFingerprint.result!!) {

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

// hook the player controller for use through integrations
onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "initialize")

// seek method
val seekFingerprintResultMethod = SeekFingerprint.also { it.resolve(context, classDef) }.result!!.method
val seekFingerprintResultMethod =
SeekFingerprint.also { it.resolve(context, classDef) }.resultOrThrow().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(MdxPlayerDirectorSetVideoStageFingerprint.resultOrThrow()) {
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.
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
val initThisIndex = mdxInitMethod.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT && getReference<MethodReference>()?.name == "<init>"
}
mdxInitInsertRegister = mdxInitMethod.getInstruction<FiveRegisterInstruction>(initThisIndex).registerC
mdxInitInsertIndex = initThisIndex + 1

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

// MDX seek method
val mdxSeekFingerprintResultMethod =
MdxSeekFingerprint.apply { resolve(context, classDef) }.resultOrThrow().method

// 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 All @@ -103,7 +118,7 @@ object VideoInformationPatch : BytecodePatch(

addInstruction(
videoLengthMethodResult.scanResult.patternScanResult!!.endIndex,
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V"
"invoke-static { v$videoLengthRegister, v$dummyRegisterForLong }, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V"
)
}
}
Expand Down Expand Up @@ -158,6 +173,35 @@ object VideoInformationPatch : BytecodePatch(
userSelectedPlaybackSpeedHook(INTEGRATIONS_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
}

private fun generateSeekMethodHelper(seekMethod: Method): MutableMethod {

// create helper method
val generatedMethod = 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
generatedMethod.addInstructions(
0,
"""
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual { p0, p1, p2, v0 }, $seekMethod
move-result p1
return p1
"""
)
return generatedMethod
}

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

Expand All @@ -180,6 +224,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 MdxPlayerDirectorSetVideoStageFingerprint : MethodFingerprint(
strings = listOf("MdxDirector setVideoStage ad should be null when videoStage is not an Ad state ")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
import com.android.tools.smali.dexlib2.Opcode

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"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.RETURN,
),
customFingerprint = { methodDef, _ ->
// The instruction count is necessary here to avoid matching the relative version
// of the seek method we're after, which has the same function signature as the
// regular one, is in the same class, and even has the exact same 3 opcodes pattern
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
methodDef.implementation!!.instructions.count() == 3
LisoUseInAIKyrios marked this conversation as resolved.
Show resolved Hide resolved
}
)
Loading