fix: Readd swipe-controls patch (#123)

This commit is contained in:
TheJeterLP 2022-07-06 20:27:28 +02:00 committed by GitHub
parent 54d67a45d5
commit 7f2a2b2ee4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 301 additions and 0 deletions

View file

@ -1 +1,2 @@
Repo for all ReVanced patches

View file

@ -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 } <hook>
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)

View file

@ -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

View file

@ -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
)
)

View file

@ -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")
}
}
}