From 7f2a2b2ee4e6045d53aba4e7705431b643981107 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Wed, 6 Jul 2022 20:27:28 +0200 Subject: [PATCH] fix: Readd `swipe-controls` patch (#123) --- README.md | 1 + .../app/revanced/extensions/Extensions.kt | 120 +++++++++++++++++ .../annotation/FensterCompatibility.kt | 14 ++ .../UpdatePlayerTypeFingerprint.kt | 45 +++++++ .../interaction/fenster/patch/FensterPatch.kt | 121 ++++++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/annotation/FensterCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/fingerprints/UpdatePlayerTypeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/patch/FensterPatch.kt diff --git a/README.md b/README.md index 865db533a..e4aacd311 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ Repo for all ReVanced patches + diff --git a/src/main/kotlin/app/revanced/extensions/Extensions.kt b/src/main/kotlin/app/revanced/extensions/Extensions.kt index 45a93519d..cf3745251 100644 --- a/src/main/kotlin/app/revanced/extensions/Extensions.kt +++ b/src/main/kotlin/app/revanced/extensions/Extensions.kt @@ -1,7 +1,18 @@ package app.revanced.extensions +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.toInstruction +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.BuilderInstruction11n +import org.jf.dexlib2.builder.instruction.BuilderInstruction11x +import org.jf.dexlib2.builder.instruction.BuilderInstruction21t +import org.jf.dexlib2.builder.instruction.BuilderInstruction35c +import org.jf.dexlib2.immutable.reference.ImmutableMethodReference import org.w3c.dom.Node internal fun MutableMethodImplementation.injectHideCall( @@ -14,6 +25,115 @@ internal fun MutableMethodImplementation.injectHideCall( ) } +/** + * Insert an event hook at the top of the method. If the hook returns true, the event is consumed and the method will return with true + * + * the hook method MUST return a boolean and MUST take two parameters, like so: + * fun hook(thisRef: Object, eventData: Object): Boolean {} + * + * The final injected code will resemble the following logic: + * if( YouHook(this, event) ) { return true; } + * ... + * + * @param hookRef reference to the hook method + */ +internal fun MutableMethod.injectConsumableEventHook(hookRef: ImmutableMethodReference) { + val isStaticMethod = AccessFlags.STATIC.isSet(this.accessFlags) + this.implementation?.let { impl -> + // create label to index 0 to continue to the normal program flow + val lblContinueNormalFlow = impl.newLabelForIndex(0) + + // define registers + /** V0 */ + val regV0 = 0 + + /** this */ + val regP0 = impl.registerCount - this.parameters.size - (if (isStaticMethod) 0 else 1) + + /** motionEvent */ + val regP1 = regP0 + 1 + + // insert instructions at the start of the method: + // if( Hook(this, event) ) { return true; } + impl.addInstructions( + 0, listOf( + // invoke-static { p0, p1 } + BuilderInstruction35c( + Opcode.INVOKE_STATIC, + 2, + regP0, + regP1, + 0, 0, 0, + hookRef + ), + + // move-result v0 + BuilderInstruction11x( + Opcode.MOVE_RESULT, + regV0 + ), + + // if-eqz v0, :continue_normal_flow + BuilderInstruction21t( + Opcode.IF_EQZ, + regV0, + lblContinueNormalFlow + ), + + // const/4 v0, 0x1 + BuilderInstruction11n( + Opcode.CONST_4, + regV0, + 0x1 + ), + + // return v0 + BuilderInstruction11x( + Opcode.RETURN, + regV0 + ) + + // :continue_normal_flow + ) + ) + } +} + +/** + * Insert instructions into a named method + * + * @param targetClass the name of the class of which the method is a member + * @param targetMethod the name of the method to insert into + * @param index index to insert the instructions at. If the index is negative, it is used as an offset to the last method (so -1 inserts at the end of the method) + * @param instructions the smali instructions to insert (they'll be compiled by MutableMethod.addInstructions) + */ +internal fun BytecodeData.injectIntoNamedMethod( + targetClass: String, + targetMethod: String, + index: Int, + instructions: String +) { + var injections = 0 + this.classes.filter { it.type.endsWith("$targetClass;") }.forEach { classDef -> + this.proxy(classDef).resolve().methods.filter { it.name == targetMethod }.forEach { methodDef -> + // if index is negative, interpret as an offset from the back + var insertIndex = index + if (insertIndex < 0) { + insertIndex += methodDef.implementation!!.instructions.size + } + + // insert instructions + methodDef.addInstructions(insertIndex, instructions) + injections++ + } + } + + // fail if nothing was injected + if (injections <= 0) { + throw PatchResultError("failed to inject into $targetClass.$targetMethod: no targets were found") + } +} + internal fun Node.doRecursively(action: (Node) -> Unit) { action(this) for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/annotation/FensterCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/annotation/FensterCompatibility.kt new file mode 100644 index 000000000..1bba30228 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/annotation/FensterCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.interaction.fenster.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +//TODO the patch may be compatible with more versions, but this is the one i'm testing on right now... +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.24.34") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FensterCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/fingerprints/UpdatePlayerTypeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/fingerprints/UpdatePlayerTypeFingerprint.kt new file mode 100644 index 000000000..eb6a5045f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/fingerprints/UpdatePlayerTypeFingerprint.kt @@ -0,0 +1,45 @@ +package app.revanced.patches.youtube.interaction.fenster.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.interaction.fenster.annotation.FensterCompatibility +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@Name("update-player-type-fingerprint") +@MatchingMethod( + "LYoutubePlayerOverlaysLayout;", + "nM" +) +@FuzzyPatternScanMethod(2) +@FensterCompatibility +@Version("0.0.1") +object UpdatePlayerTypeFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + null, + listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.IF_NE, + Opcode.RETURN_VOID, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.CONST_4, + Opcode.INVOKE_STATIC, + Opcode.RETURN_VOID, + Opcode.CONST_4, + Opcode.INVOKE_STATIC, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/patch/FensterPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/patch/FensterPatch.kt new file mode 100644 index 000000000..07cdf9275 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/fenster/patch/FensterPatch.kt @@ -0,0 +1,121 @@ +package app.revanced.patches.youtube.interaction.fenster.patch + +import app.revanced.extensions.injectConsumableEventHook +import app.revanced.extensions.injectIntoNamedMethod +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Dependencies +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.impl.BytecodePatch +import app.revanced.patches.youtube.interaction.fenster.annotation.FensterCompatibility +import app.revanced.patches.youtube.interaction.fenster.fingerprints.UpdatePlayerTypeFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.immutable.reference.ImmutableMethodReference + +@Patch +@Name("fenster-swipe-controls") +@Description("volume and brightness swipe controls") +@FensterCompatibility +@Version("0.0.1") +@Dependencies(dependencies = [IntegrationsPatch::class]) +class FensterPatch : BytecodePatch( + listOf( + UpdatePlayerTypeFingerprint + ) +) { + override fun execute(data: BytecodeData): PatchResult { + // hook WatchWhileActivity.onStart (main activity lifecycle hook) + data.injectIntoNamedMethod( + "com/google/android/apps/youtube/app/watchwhile/WatchWhileActivity", + "onStart", + 0, + "invoke-static { p0 }, Lapp/revanced/integrations/patches/FensterSwipePatch;->WatchWhileActivity_onStartHookEX(Ljava/lang/Object;)V" + ) + + // hook YoutubePlayerOverlaysLayout.onFinishInflate (player overlays init hook) + data.injectIntoNamedMethod( + "com/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout", + "onFinishInflate", + -2, + "invoke-static { p0 }, Lapp/revanced/integrations/patches/FensterSwipePatch;->YouTubePlayerOverlaysLayout_onFinishInflateHookEX(Ljava/lang/Object;)V" + ) + + // hook YoutubePlayerOverlaysLayout.UpdatePlayerType + injectUpdatePlayerTypeHook( + UpdatePlayerTypeFingerprint.result!!, + "com/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout" + ) + + // hook NextGenWatchLayout.onTouchEvent and NextGenWatchLayout.onInterceptTouchEvent (player touch event hook) + injectWatchLayoutTouchHooks( + data, + "com/google/android/apps/youtube/app/watch/nextgenwatch/ui/NextGenWatchLayout" + ) + + return PatchResultSuccess() + } + + @Suppress("SameParameterValue") + private fun injectUpdatePlayerTypeHook(fingerPrintResult: MethodFingerprintResult, targetClass: String) { + // validate fingerprint found the right class + if (!fingerPrintResult.classDef.type.endsWith("$targetClass;")) { + throw PatchResultError("$targetClass.UpdatePlayerType fingerprint could not be validated") + } + + // insert the hook + fingerPrintResult.mutableMethod.addInstruction( + 0, + "invoke-static { p1 }, Lapp/revanced/integrations/patches/FensterSwipePatch;->YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(Ljava/lang/Object;)V" + ) + } + + /** + * Inject onTouch event hooks into the watch layout class + * + * @param data bytecode data + * @param targetClass watch layout class name + */ + @Suppress("SameParameterValue") + private fun injectWatchLayoutTouchHooks(data: BytecodeData, targetClass: String) { + var touchHooksCount = 0 + data.classes.filter { it.type.endsWith("$targetClass;") }.forEach { classDef -> + // hook onTouchEvent + data.proxy(classDef).resolve().methods.filter { it.name == "onTouchEvent" }.forEach { methodDef -> + touchHooksCount++ + methodDef.injectConsumableEventHook( + ImmutableMethodReference( + "Lapp/revanced/integrations/patches/FensterSwipePatch;", + "NextGenWatchLayout_onTouchEventHookEX", + listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), + "Z" + ) + ) + } + + // hook onInterceptTouchEvent + data.proxy(classDef).resolve().methods.filter { it.name == "onInterceptTouchEvent" }.forEach { methodDef -> + touchHooksCount++ + methodDef.injectConsumableEventHook( + ImmutableMethodReference( + "Lapp/revanced/integrations/patches/FensterSwipePatch;", + "NextGenWatchLayout_onInterceptTouchEventHookEX", + listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), + "Z" + ) + ) + } + } + + // fail if no touch hooks were inserted + if (touchHooksCount <= 0) { + throw PatchResultError("failed to inject onTouchEvent hook into NextGenWatchLayout: none found") + } + } +} \ No newline at end of file