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 dc7f343b9..017862f7d 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 @@ -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 @@ -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.", @@ -32,6 +38,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil object VideoInformationPatch : BytecodePatch( setOf( PlayerInitFingerprint, + MdxPlayerDirectorSetVideoStageFingerprint, CreateVideoPlayerSeekbarFingerprint, PlayerControllerSetTimeReferenceFingerprint, OnPlaybackSpeedItemClickFingerprint @@ -42,12 +49,16 @@ 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 @@ -55,44 +66,48 @@ object VideoInformationPatch : BytecodePatch( 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 + val initThisIndex = mdxInitMethod.indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_DIRECT && getReference()?.name == "" + } + mdxInitInsertRegister = mdxInitMethod.getInstruction(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!! @@ -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" ) } } @@ -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") @@ -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. diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxPlayerDirectorSetVideoStageFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxPlayerDirectorSetVideoStageFingerprint.kt new file mode 100644 index 000000000..3edac97f7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxPlayerDirectorSetVideoStageFingerprint.kt @@ -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 ") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekFingerprint.kt new file mode 100644 index 000000000..6bec477f7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekFingerprint.kt @@ -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( + 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. + methodDef.implementation!!.instructions.count() == 3 + } +) \ No newline at end of file