From 15449819ff74b636fb2fa6aacd770142c51d2e5d Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 2 Aug 2024 09:40:28 -0400 Subject: [PATCH] fix(YouTube - SponsorBlock): Improve create segment manual seek accuracy (#3491) --- api/revanced-patches.api | 1 + .../misc/imageurlhook/CronetImageUrlHook.kt | 10 +- .../information/VideoInformationPatch.kt | 104 ++++++++++-------- .../fingerprints/MdxSeekFingerprint.kt | 3 + .../MdxSeekRelativeFingerprint.kt | 19 ++++ .../fingerprints/SeekFingerprint.kt | 3 + .../fingerprints/SeekRelativeFingerprint.kt | 21 ++++ .../kotlin/app/revanced/util/BytecodeUtils.kt | 6 + 8 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekRelativeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekRelativeFingerprint.kt diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 1c47eb4ea..14a4e3230 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -2104,6 +2104,7 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat } public final class app/revanced/util/BytecodeUtilsKt { + public static final fun alsoResolve (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/MethodFingerprintResult; public static final fun containsWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z public static final fun findMutableMethodOf (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod; public static final fun findOpcodeIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List; diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHook.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHook.kt index d300327f2..d85b5f6a1 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHook.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHook.kt @@ -16,6 +16,7 @@ import app.revanced.patches.youtube.misc.imageurlhook.fingerprints.cronet.reques import app.revanced.patches.youtube.misc.imageurlhook.fingerprints.cronet.request.callback.OnResponseStartedFingerprint import app.revanced.patches.youtube.misc.imageurlhook.fingerprints.cronet.request.callback.OnSucceededFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.util.alsoResolve import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -84,17 +85,14 @@ object CronetImageUrlHook : BytecodePatch( } override fun execute(context: BytecodeContext) { - fun MethodFingerprint.alsoResolve(fingerprint: MethodFingerprint) = - also { resolve(context, fingerprint.resultOrThrow().classDef) }.resultOrThrow() - loadImageUrlMethod = MessageDigestImageUrlFingerprint - .alsoResolve(MessageDigestImageUrlParentFingerprint).mutableMethod + .alsoResolve(context, MessageDigestImageUrlParentFingerprint).mutableMethod loadImageSuccessCallbackMethod = OnSucceededFingerprint - .alsoResolve(OnResponseStartedFingerprint).mutableMethod + .alsoResolve(context, OnResponseStartedFingerprint).mutableMethod loadImageErrorCallbackMethod = OnFailureFingerprint - .alsoResolve(OnResponseStartedFingerprint).mutableMethod + .alsoResolve(context, OnResponseStartedFingerprint).mutableMethod // The URL is required for the failure callback hook, but the URL field is obfuscated. // Add a helper get method that returns the URL field. 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 017862f7d..88b90ba33 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 @@ -7,12 +7,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch 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.alsoResolve import app.revanced.util.exception import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow @@ -45,9 +47,11 @@ object VideoInformationPatch : BytecodePatch( ) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/VideoInformation;" + private const val INTEGRATIONS_PLAYER_INTERFACE = "Lapp/revanced/integrations/youtube/patches/VideoInformation${'$'}PlaybackController;" private lateinit var playerInitMethod: MutableMethod - private var playerInitInsertIndex = 4 + private var playerInitInsertIndex = -1 + private var playerInitInsertRegister = -1 private lateinit var mdxInitMethod: MutableMethod private var mdxInitInsertIndex = -1 @@ -70,42 +74,43 @@ object VideoInformationPatch : BytecodePatch( with(PlayerInitFingerprint.resultOrThrow()) { playerInitMethod = mutableClass.methods.first { MethodUtil.isConstructor(it) } - // hook the player controller for use through integrations + // find the location of the first invoke-direct call and extract the register storing the 'this' object reference. + val initThisIndex = playerInitMethod.indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_DIRECT && getReference()?.name == "" + } + playerInitInsertRegister = playerInitMethod.getInstruction(initThisIndex).registerC + playerInitInsertIndex = initThisIndex + 1 + + // Hook the player controller for use through integrations. onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "initialize") - // seek method val seekFingerprintResultMethod = - SeekFingerprint.also { it.resolve(context, classDef) }.resultOrThrow().method + SeekFingerprint.alsoResolve(context, PlayerInitFingerprint).method + val seekRelativeFingerprintResultMethod = + SeekRelativeFingerprint.alsoResolve(context, PlayerInitFingerprint).method - // create helper method - val seekHelperMethod = generateSeekMethodHelper(seekFingerprintResultMethod) - - // add the seekTo method to the class for the integrations to call - mutableClass.methods.add(seekHelperMethod) + // Create integrations interface methods. + addSeekInterfaceMethods(mutableClass, seekFingerprintResultMethod, seekRelativeFingerprintResultMethod) } 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 + // 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 + MdxSeekFingerprint.alsoResolve(context, MdxPlayerDirectorSetVideoStageFingerprint).method + val mdxSeekRelativeFingerprintResultMethod = + MdxSeekRelativeFingerprint.alsoResolve(context, MdxPlayerDirectorSetVideoStageFingerprint).method - // create helper method - val mdxSeekHelperMethod = generateSeekMethodHelper(mdxSeekFingerprintResultMethod) - - // add the seekTo method to the class for the integrations to call - mutableClass.methods.add(mdxSeekHelperMethod) + addSeekInterfaceMethods(mutableClass, mdxSeekFingerprintResultMethod, mdxSeekRelativeFingerprintResultMethod) } with(CreateVideoPlayerSeekbarFingerprint.result!!) { @@ -173,33 +178,42 @@ object VideoInformationPatch : BytecodePatch( userSelectedPlaybackSpeedHook(INTEGRATIONS_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed") } - private fun generateSeekMethodHelper(seekMethod: Method): MutableMethod { + private fun addSeekInterfaceMethods(targetClass: MutableClass, seekToMethod: Method, seekToRelativeMethod: Method) { + // Add the interface and methods that integrations calls. + targetClass.interfaces.add(INTEGRATIONS_PLAYER_INTERFACE) - // 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() + arrayOf( + seekToMethod to "seekTo", + seekToRelativeMethod to "seekToRelative" + ).forEach { (method, name) -> + // Add interface method. + // Get enum type for the seek helper method. + val seekSourceEnumType = method.parameterTypes[1].toString() - // get enum type for the seek helper method - val seekSourceEnumType = seekMethod.parameterTypes[1].toString() + val interfaceImplementation = ImmutableMethod( + targetClass.type, + name, + listOf(ImmutableMethodParameter("J", null, "time")), + "Z", + AccessFlags.PUBLIC or AccessFlags.FINAL, + null, null, + MutableMethodImplementation(4) + ).toMutable() - // 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 + // Insert helper method instructions. + interfaceImplementation.addInstructions( + 0, + """ + # first enum (field a) is SEEK_SOURCE_UNKNOWN + sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType + invoke-virtual { p0, p1, p2, v0 }, $method + move-result p1 + return p1 + """ + ) + + targetClass.methods.add(interfaceImplementation) + } } private fun MutableMethod.insert(insertIndex: Int, register: String, descriptor: String) = @@ -220,8 +234,8 @@ object VideoInformationPatch : BytecodePatch( internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) = playerInitMethod.insert( playerInitInsertIndex++, - "v0", - "$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V" + "v$playerInitInsertRegister", + "$targetMethodClass->$targetMethodName($INTEGRATIONS_PLAYER_INTERFACE)V" ) /** @@ -234,7 +248,7 @@ object VideoInformationPatch : BytecodePatch( mdxInitMethod.insert( mdxInitInsertIndex++, "v$mdxInitInsertRegister", - "$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V" + "$targetMethodClass->$targetMethodName($INTEGRATIONS_PLAYER_INTERFACE)V" ) /** 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 index 6bec477f7..61269e9e8 100644 --- 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 @@ -5,6 +5,9 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +/** + * Resolves using class found in [MdxPlayerDirectorSetVideoStageFingerprint]. + */ internal object MdxSeekFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "Z", diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekRelativeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekRelativeFingerprint.kt new file mode 100644 index 000000000..3fd89abf1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/MdxSeekRelativeFingerprint.kt @@ -0,0 +1,19 @@ +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 + +/** + * Resolves using class found in [MdxPlayerDirectorSetVideoStageFingerprint]. + */ +internal object MdxSeekRelativeFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + parameters = listOf("J", "L"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekFingerprint.kt index 5747810e5..ef2d5dcb7 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekFingerprint.kt @@ -3,6 +3,9 @@ package app.revanced.patches.youtube.video.information.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint +/** + * Resolves using class found in [PlayerInitFingerprint]. + */ internal object SeekFingerprint : MethodFingerprint( strings = listOf("Attempting to seek during an ad") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekRelativeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekRelativeFingerprint.kt new file mode 100644 index 000000000..05c89b933 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekRelativeFingerprint.kt @@ -0,0 +1,21 @@ +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 + +/** + * Resolves using class found in [PlayerInitFingerprint]. + */ +internal object SeekRelativeFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + parameters = listOf("J", "L"), + opcodes = listOf( + Opcode.ADD_LONG_2ADDR, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.RETURN + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index f0ac585cc..0f81d8d4e 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -249,3 +249,9 @@ fun Iterable.returnEarly(bool: Boolean = false) = forEach { f fun List.returnEarly(bool: Boolean = false) = forEach { fingerprint -> fingerprint.returnEarly(bool) } + +/** + * Resolves this fingerprint using the classDef of a parent fingerprint. + */ +fun MethodFingerprint.alsoResolve(context: BytecodeContext, parentFingerprint: MethodFingerprint) = + also { resolve(context, parentFingerprint.resultOrThrow().classDef) }.resultOrThrow()