diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 5ca19d08bf..ed2f78f371 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -1850,10 +1850,6 @@ public final class app/revanced/patches/youtube/layout/startpage/ChangeStartPage public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } -public final class app/revanced/patches/youtube/layout/startpage/fingerprints/StartActivityFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint { - public static final field INSTANCE Lapp/revanced/patches/youtube/layout/startpage/fingerprints/StartActivityFingerprint; -} - public final class app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V @@ -2203,12 +2199,27 @@ public final class app/revanced/util/BytecodeUtilsKt { 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; public static final fun findOpcodeIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun forEachLiteralValueInstruction (Lapp/revanced/patcher/data/BytecodeContext;JLkotlin/jvm/functions/Function2;)V public static final fun getException (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/patch/PatchException; + public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)I + public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)I + public static synthetic fun indexOfFirstInstruction$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstruction$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I + public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)I + public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I + public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I + public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I + public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I + public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I + public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I + public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I + public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I + public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I + public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstWideLiteralInstructionValueReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt index 60eb637c47..b1522e880c 100644 --- a/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt @@ -13,7 +13,7 @@ import app.revanced.patches.tiktok.shared.fingerprints.OnRenderFirstFrameFingerp import app.revanced.util.exception import app.revanced.util.indexOfFirstInstructionOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction @Patch( name = "Remember clear display", @@ -34,8 +34,8 @@ object RememberClearDisplayPatch : BytecodePatch( OnClearDisplayEventFingerprint.result?.mutableMethod?.let { // region Hook the "Clear display" configuration save event to remember the state of clear display. - val isEnabledIndex = it.indexOfFirstInstructionOrThrow { opcode == Opcode.IGET_BOOLEAN } + 1 - val isEnabledRegister = it.getInstruction(isEnabledIndex - 1).registerA + val isEnabledIndex = it.indexOfFirstInstructionOrThrow(Opcode.IGET_BOOLEAN) + 1 + val isEnabledRegister = it.getInstruction(isEnabledIndex - 1).registerA it.addInstructions( isEnabledIndex, diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index fd6ffde4b9..9cb064b597 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -26,30 +26,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/getpremium/HideGetPremiumPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/getpremium/HideGetPremiumPatch.kt index a36063d3d3..4cb81e2fbd 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ad/getpremium/HideGetPremiumPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/getpremium/HideGetPremiumPatch.kt @@ -20,30 +20,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt index b2df5d82b1..aa3a7f71eb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt @@ -25,30 +25,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt index cea3988c28..ac5d7499b5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt @@ -19,24 +19,11 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch CompatiblePackage( "com.google.android.youtube", [ - "18.48.39", + "18.38.44", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt index 83b391cb8f..88df4beae6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt @@ -22,30 +22,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index 6c8119fe93..7d2dd4a9ac 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -25,24 +25,11 @@ import app.revanced.util.resultOrThrow CompatiblePackage( "com.google.android.youtube", [ - "18.48.39", + "18.38.44", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/DisablePreciseSeekingGesturePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/DisablePreciseSeekingGesturePatch.kt index dd3a07ae7f..04be050176 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/DisablePreciseSeekingGesturePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/DisablePreciseSeekingGesturePatch.kt @@ -24,30 +24,11 @@ import app.revanced.util.alsoResolve CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSeekbarTappingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSeekbarTappingPatch.kt index eaae9af877..e37eb7f38c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSeekbarTappingPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSeekbarTappingPatch.kt @@ -27,27 +27,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference CompatiblePackage( "com.google.android.youtube", [ - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", + // 18.38.44 patches but crashes on startup. "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSlideToSeekPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSlideToSeekPatch.kt index e6ed839e4b..5b40947aac 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSlideToSeekPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/EnableSlideToSeekPatch.kt @@ -4,46 +4,37 @@ import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.interaction.seekbar.fingerprints.DoubleSpeedSeekNoticeFingerprint +import app.revanced.patches.youtube.interaction.seekbar.fingerprints.DisableFastForwardLegacyFingerprint +import app.revanced.patches.youtube.interaction.seekbar.fingerprints.DisableFastForwardGestureFingerprint +import app.revanced.patches.youtube.interaction.seekbar.fingerprints.DisableFastForwardNoticeFingerprint import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SlideToSeekFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch -import app.revanced.util.exception +import app.revanced.util.getReference +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( name = "Enable slide to seek", - description = "Adds an option to enable slide to seek instead of playing at 2x speed when pressing and holding in the video player. Including this patch may cause issues with tapping or double tapping the video player overlay.", + description = "Adds an option to enable slide to seek instead of playing at 2x speed when pressing and holding in the video player. Including this patch may cause issues with the video player overlay, such as missing buttons and ignored taps and double taps.", dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", + "18.38.44", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ], @@ -53,10 +44,13 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction object EnableSlideToSeekPatch : BytecodePatch( setOf( SlideToSeekFingerprint, - DoubleSpeedSeekNoticeFingerprint + DisableFastForwardLegacyFingerprint, + DisableFastForwardGestureFingerprint, + DisableFastForwardNoticeFingerprint ) ) { - private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/SlideToSeekPatch;" + private const val INTEGRATIONS_METHOD_DESCRIPTOR = + "Lapp/revanced/integrations/youtube/patches/SlideToSeekPatch;->isSlideToSeekDisabled(Z)Z" override fun execute(context: BytecodeContext) { AddResourcesPatch(this::class) @@ -65,27 +59,74 @@ object EnableSlideToSeekPatch : BytecodePatch( SwitchPreference("revanced_slide_to_seek") ) - arrayOf( - // Restore the behaviour to slide to seek. - SlideToSeekFingerprint, - // Disable the double speed seek notice. - DoubleSpeedSeekNoticeFingerprint - ).map { - it.result ?: throw it.exception - }.forEach { - val insertIndex = it.scanResult.patternScanResult!!.endIndex + 1 + var modifiedMethods = false - it.mutableMethod.apply { - val isEnabledRegister = getInstruction(insertIndex).registerA + // Restore the behaviour to slide to seek. + SlideToSeekFingerprint.resultOrThrow().let { + val checkIndex = it.scanResult.patternScanResult!!.startIndex + val checkReference = it.mutableMethod + .getInstruction(checkIndex).getReference()!! - addInstructions( - insertIndex, - """ - invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isSlideToSeekDisabled()Z - move-result v$isEnabledRegister - """ - ) + // A/B check method was only called on this class. + it.mutableClass.methods.forEach { method -> + method.implementation!!.instructions.forEachIndexed { index, instruction -> + if (instruction.opcode == Opcode.INVOKE_VIRTUAL && + instruction.getReference() == checkReference + ) { + method.apply { + val targetRegister = getInstruction(index + 1).registerA + + addInstructions( + index + 2, + """ + invoke-static { v$targetRegister }, $INTEGRATIONS_METHOD_DESCRIPTOR + move-result v$targetRegister + """ + ) + } + + modifiedMethods = true + } + } + } + } + + if (!modifiedMethods) throw PatchException("Could not find methods to modify") + + // Disable the double speed seek gesture. + if (!VersionCheckPatch.is_19_17_or_greater) { + DisableFastForwardLegacyFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.endIndex + 1 + val targetRegister = getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, + """ + invoke-static { v$targetRegister }, $INTEGRATIONS_METHOD_DESCRIPTOR + move-result v$targetRegister + """ + ) + } } + } else { + arrayOf( + DisableFastForwardGestureFingerprint, + DisableFastForwardNoticeFingerprint + ).forEach { it.resultOrThrow().let { + it.mutableMethod.apply { + val targetIndex = it.scanResult.patternScanResult!!.endIndex + val targetRegister = getInstruction(targetIndex).registerA + + addInstructions( + targetIndex + 1, + """ + invoke-static { v$targetRegister }, $INTEGRATIONS_METHOD_DESCRIPTOR + move-result v$targetRegister + """ + ) + } + }} } } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardGestureFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardGestureFingerprint.kt new file mode 100644 index 0000000000..a1c5bf9870 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardGestureFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.youtube.interaction.seekbar.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 DisableFastForwardGestureFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + parameters = emptyList(), + opcodes = listOf( + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT + ), + customFingerprint = { methodDef, classDef -> + methodDef.implementation!!.instructions.count() > 30 && + classDef.type.endsWith("/NextGenWatchLayout;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DoubleSpeedSeekNoticeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardLegacyFingerprint.kt similarity index 79% rename from src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DoubleSpeedSeekNoticeFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardLegacyFingerprint.kt index eee74fde59..1762c20431 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DoubleSpeedSeekNoticeFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardLegacyFingerprint.kt @@ -3,7 +3,7 @@ package app.revanced.patches.youtube.interaction.seekbar.fingerprints import app.revanced.util.patch.LiteralValueFingerprint import com.android.tools.smali.dexlib2.Opcode -internal object DoubleSpeedSeekNoticeFingerprint : LiteralValueFingerprint( +internal object DisableFastForwardLegacyFingerprint : LiteralValueFingerprint( returnType = "Z", parameters = emptyList(), opcodes = listOf(Opcode.MOVE_RESULT), diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardNoticeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardNoticeFingerprint.kt new file mode 100644 index 0000000000..99be205e87 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/DisableFastForwardNoticeFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.youtube.interaction.seekbar.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 DisableFastForwardNoticeFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = emptyList(), + opcodes = listOf( + Opcode.CHECK_CAST, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT + ), + strings = listOf("Failed to easy seek haptics vibrate") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt index ddb8bf47d9..3989aa2d5c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt @@ -2,10 +2,9 @@ package app.revanced.patches.youtube.interaction.seekbar.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction - internal object SeekbarTappingFingerprint : MethodFingerprint( returnType = "Z", @@ -21,14 +20,6 @@ internal object SeekbarTappingFingerprint : MethodFingerprint( customFingerprint = custom@{ methodDef, _ -> if (methodDef.name != "onTouchEvent") return@custom false - methodDef.implementation!!.instructions.any { instruction -> - if (instruction.opcode != Opcode.CONST) return@any false - - val literal = (instruction as NarrowLiteralInstruction).narrowLiteral - - // onTouchEvent method contains a CONST instruction - // with this literal making it unique with the rest of the properties of this fingerprint. - literal == Integer.MAX_VALUE - } + methodDef.containsWideLiteralInstructionValue(Integer.MAX_VALUE.toLong()) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SlideToSeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SlideToSeekFingerprint.kt index b618ea50f1..c3af701bfc 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SlideToSeekFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SlideToSeekFingerprint.kt @@ -1,11 +1,19 @@ package app.revanced.patches.youtube.interaction.seekbar.fingerprints +import app.revanced.patcher.extensions.or import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode internal object SlideToSeekFingerprint : LiteralValueFingerprint( - returnType = "Z", - parameters = emptyList(), - opcodes = listOf(Opcode.MOVE_RESULT), - literalSupplier = { 45411329 } + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Landroid/view/View;", "F"), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.GOTO_16 + ), + literalSupplier = { 67108864 } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SwipingUpGestureParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SwipingUpGestureParentFingerprint.kt index cb33e72a23..98a03b3cbf 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SwipingUpGestureParentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SwipingUpGestureParentFingerprint.kt @@ -1,11 +1,8 @@ package app.revanced.patches.youtube.interaction.seekbar.fingerprints -import app.revanced.patcher.extensions.or import app.revanced.util.patch.LiteralValueFingerprint -import com.android.tools.smali.dexlib2.AccessFlags internal object SwipingUpGestureParentFingerprint : LiteralValueFingerprint( - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "Z", parameters = listOf(), literalSupplier = { 45379021 } diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsBytecodePatch.kt index 819ea52cad..63cffeb675 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsBytecodePatch.kt @@ -26,30 +26,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt index fabd8cc3ae..f5ea004743 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt @@ -24,30 +24,11 @@ import app.revanced.util.exception CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt index 1da88f35cb..f2994c824c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt @@ -23,30 +23,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/autoplay/HideAutoplayButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/autoplay/HideAutoplayButtonPatch.kt index 6446f32a43..161a3a2b3c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/autoplay/HideAutoplayButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/autoplay/HideAutoplayButtonPatch.kt @@ -34,30 +34,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/captions/HideCaptionsButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/captions/HideCaptionsButtonPatch.kt index e346945db4..bf41baa3be 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/captions/HideCaptionsButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/captions/HideCaptionsButtonPatch.kt @@ -24,30 +24,11 @@ import com.android.tools.smali.dexlib2.Opcode CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationButtonsPatch.kt index 10e8d36045..3ef0caba2a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationButtonsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationButtonsPatch.kt @@ -37,30 +37,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsPatch.kt index 2199b05d8e..9581f8f8ac 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsPatch.kt @@ -1,20 +1,23 @@ package app.revanced.patches.youtube.layout.buttons.player.hide import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.layout.buttons.player.hide.HidePlayerButtonsPatch.ParameterOffsets.HAS_NEXT -import app.revanced.patches.youtube.layout.buttons.player.hide.HidePlayerButtonsPatch.ParameterOffsets.HAS_PREVIOUS -import app.revanced.patches.youtube.layout.buttons.player.hide.fingerprints.PlayerControlsVisibilityModelFingerprint +import app.revanced.patches.youtube.layout.buttons.player.hide.fingerprints.PlayerControlsPreviousNextOverlayTouchFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch -import app.revanced.util.exception -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( name = "Hide player buttons", @@ -22,43 +25,25 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc dependencies = [ IntegrationsPatch::class, SettingsPatch::class, - AddResourcesPatch::class + AddResourcesPatch::class, + HidePlayerButtonsResourcePatch::class, ], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] ) @Suppress("unused") object HidePlayerButtonsPatch : BytecodePatch( - setOf(PlayerControlsVisibilityModelFingerprint) + setOf(PlayerControlsPreviousNextOverlayTouchFingerprint) ) { override fun execute(context: BytecodeContext) { AddResourcesPatch(this::class) @@ -67,29 +52,23 @@ object HidePlayerButtonsPatch : BytecodePatch( SwitchPreference("revanced_hide_player_buttons") ) - PlayerControlsVisibilityModelFingerprint.result?.apply { - val callIndex = scanResult.patternScanResult!!.endIndex - val callInstruction = mutableMethod.getInstruction(callIndex) + PlayerControlsPreviousNextOverlayTouchFingerprint.resultOrThrow().mutableMethod.apply { + val resourceIndex = indexOfFirstWideLiteralInstructionValueOrThrow( + HidePlayerButtonsResourcePatch.playerControlPreviousButtonTouchArea + ) - // overriding this parameter register hides the previous and next buttons - val hasNextParameterRegister = callInstruction.startRegister + HAS_NEXT - val hasPreviousParameterRegister = callInstruction.startRegister + HAS_PREVIOUS + val insertIndex = indexOfFirstInstructionOrThrow(resourceIndex) { + opcode == Opcode.INVOKE_STATIC + && getReference()?.parameterTypes?.firstOrNull() == "Landroid/view/View;" + } - mutableMethod.addInstructions( - callIndex, - """ - invoke-static { v$hasNextParameterRegister }, Lapp/revanced/integrations/youtube/patches/HidePlayerButtonsPatch;->previousOrNextButtonIsVisible(Z)Z - move-result v$hasNextParameterRegister - - invoke-static { v$hasPreviousParameterRegister }, Lapp/revanced/integrations/youtube/patches/HidePlayerButtonsPatch;->previousOrNextButtonIsVisible(Z)Z - move-result v$hasPreviousParameterRegister - """ - ) - } ?: throw PlayerControlsVisibilityModelFingerprint.exception - } + val viewRegister = getInstruction(insertIndex).registerC - private object ParameterOffsets { - const val HAS_NEXT = 5 - const val HAS_PREVIOUS = 6 + addInstruction( + insertIndex, + "invoke-static { v$viewRegister }, Lapp/revanced/integrations/youtube/patches/HidePlayerButtonsPatch;" + + "->hidePreviousNextButtons(Landroid/view/View;)V" + ) + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsResourcePatch.kt new file mode 100644 index 0000000000..7bd23b17a1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/HidePlayerButtonsResourcePatch.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.youtube.layout.buttons.player.hide + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch + +@Patch(dependencies = [ResourceMappingPatch::class]) +internal object HidePlayerButtonsResourcePatch : ResourcePatch() { + var playerControlPreviousButtonTouchArea = -1L + var playerControlNextButtonTouchArea = -1L + + override fun execute(context: ResourceContext) { + playerControlPreviousButtonTouchArea = ResourceMappingPatch[ + "id", + "player_control_previous_button_touch_area" + ] + + playerControlNextButtonTouchArea = ResourceMappingPatch[ + "id", + "player_control_next_button_touch_area" + ] + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsPreviousNextOverlayTouchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsPreviousNextOverlayTouchFingerprint.kt new file mode 100644 index 0000000000..131bf47130 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsPreviousNextOverlayTouchFingerprint.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.youtube.layout.buttons.player.hide.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.layout.buttons.player.hide.HidePlayerButtonsResourcePatch +import app.revanced.util.containsWideLiteralInstructionValue +import com.android.tools.smali.dexlib2.AccessFlags + +internal object PlayerControlsPreviousNextOverlayTouchFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + strings = listOf("1.0x"), + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue( + HidePlayerButtonsResourcePatch.playerControlPreviousButtonTouchArea + ) && methodDef.containsWideLiteralInstructionValue( + HidePlayerButtonsResourcePatch.playerControlNextButtonTouchArea + ) + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsVisibilityModelFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsVisibilityModelFingerprint.kt deleted file mode 100644 index 4aae5c27ed..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/player/hide/fingerprints/PlayerControlsVisibilityModelFingerprint.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.revanced.patches.youtube.layout.buttons.player.hide.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.Opcode - -internal object PlayerControlsVisibilityModelFingerprint : MethodFingerprint( - opcodes = listOf(Opcode.INVOKE_DIRECT_RANGE), - strings = listOf("Missing required properties:", "hasNext", "hasPrevious") -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/albumcards/AlbumCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/albumcards/AlbumCardsPatch.kt index 782999c106..937f2b509d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/albumcards/AlbumCardsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/albumcards/AlbumCardsPatch.kt @@ -22,30 +22,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/comments/CommentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/comments/CommentsPatch.kt index 51280ac183..9c595da663 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/comments/CommentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/comments/CommentsPatch.kt @@ -22,30 +22,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/crowdfundingbox/CrowdfundingBoxPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/crowdfundingbox/CrowdfundingBoxPatch.kt index 938d91eb70..e186babd7b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/crowdfundingbox/CrowdfundingBoxPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/crowdfundingbox/CrowdfundingBoxPatch.kt @@ -22,30 +22,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatch.kt index 396e9a8c35..3585f800d5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatch.kt @@ -25,30 +25,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/filterbar/HideFilterBarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/filterbar/HideFilterBarPatch.kt index 37915f48d0..7e4ad51230 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/filterbar/HideFilterBarPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/filterbar/HideFilterBarPatch.kt @@ -22,30 +22,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/floatingmicrophone/HideFloatingMicrophoneButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/floatingmicrophone/HideFloatingMicrophoneButtonPatch.kt index 697b081fc7..07b8425727 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/floatingmicrophone/HideFloatingMicrophoneButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/floatingmicrophone/HideFloatingMicrophoneButtonPatch.kt @@ -18,30 +18,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt index 0d7ccb956c..1e596071bd 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt @@ -19,29 +19,11 @@ import app.revanced.util.exception compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 9a99f3ef5c..3930d17716 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -24,6 +24,7 @@ import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.util.findOpcodeIndicesReversed import app.revanced.util.getReference +import app.revanced.util.alsoResolve import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -45,30 +46,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], @@ -202,9 +184,10 @@ object HideLayoutComponentsPatch : BytecodePatch( // region Watermark (legacy code for old versions of YouTube) - ShowWatermarkFingerprint.also { - it.resolve(context, PlayerOverlayFingerprint.resultOrThrow().classDef) - }.resultOrThrow().mutableMethod.apply { + ShowWatermarkFingerprint.alsoResolve( + context, + PlayerOverlayFingerprint + ).mutableMethod.apply { val index = implementation!!.instructions.size - 5 removeInstruction(index) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt index 92b2d5a4f8..cd162823eb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt @@ -13,6 +13,7 @@ import app.revanced.patches.youtube.layout.hide.infocards.fingerprints.Infocards import app.revanced.patches.youtube.layout.hide.infocards.fingerprints.InfocardsMethodCallFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch +import app.revanced.util.alsoResolve import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @@ -29,30 +30,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] @@ -68,9 +50,7 @@ object HideInfoCardsPatch : BytecodePatch( "Lapp/revanced/integrations/youtube/patches/components/HideInfoCardsFilterPatch;" override fun execute(context: BytecodeContext) { - InfocardsIncognitoFingerprint.also { - it.resolve(context, InfocardsIncognitoParentFingerprint.result!!.classDef) - }.result!!.mutableMethod.apply { + InfocardsIncognitoFingerprint.alsoResolve(context, InfocardsIncognitoParentFingerprint).mutableMethod.apply { val invokeInstructionIndex = implementation!!.instructions.indexOfFirst { it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal && ((it as ReferenceInstruction).reference.toString() == "Landroid/view/View;->setVisibility(I)V") diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt index 6bf5c58eb7..76ac414085 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt @@ -23,30 +23,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt index 0e7b98850f..a2fe2ad1c4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt @@ -23,27 +23,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", + // 18.43 is the earliest target this patch works. "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/seekbar/HideSeekbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/seekbar/HideSeekbarPatch.kt index 6f369184a4..0f4473523b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/seekbar/HideSeekbarPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/seekbar/HideSeekbarPatch.kt @@ -12,6 +12,7 @@ import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.shared.fingerprints.SeekbarFingerprint import app.revanced.patches.youtube.shared.fingerprints.SeekbarOnDrawFingerprint +import app.revanced.util.alsoResolve @Patch( name = "Hide seekbar", @@ -25,30 +26,11 @@ import app.revanced.patches.youtube.shared.fingerprints.SeekbarOnDrawFingerprint compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] @@ -65,9 +47,10 @@ object HideSeekbarPatch : BytecodePatch( SwitchPreference("revanced_hide_seekbar_thumbnail") ) - SeekbarFingerprint.result!!.let { - SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) } - }.result!!.mutableMethod.addInstructionsWithLabels( + SeekbarOnDrawFingerprint.alsoResolve( + context, + SeekbarFingerprint + ).mutableMethod.addInstructionsWithLabels( 0, """ const/4 v0, 0x0 diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt index e1ba723910..87986185de 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt @@ -2,6 +2,7 @@ package app.revanced.patches.youtube.layout.hide.shorts import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage @@ -13,10 +14,15 @@ import app.revanced.patches.youtube.layout.hide.shorts.fingerprints.* import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch import app.revanced.patches.youtube.misc.navigation.NavigationBarHookPatch -import app.revanced.util.exception +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch +import app.revanced.util.forEachLiteralValueInstruction +import app.revanced.util.alsoResolve import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValue import app.revanced.util.indexOfIdResourceOrThrow import app.revanced.util.injectHideViewCall +import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -32,35 +38,17 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference HideShortsComponentsResourcePatch::class, ResourceMappingPatch::class, NavigationBarHookPatch::class, + VersionCheckPatch::class ], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], @@ -70,7 +58,8 @@ object HideShortsComponentsPatch : BytecodePatch( setOf( CreateShortsButtonsFingerprint, ReelConstructorFingerprint, - BottomNavigationBarFingerprint, + ShortsBottomBarContainerFingerprint, + LegacyRenderBottomNavigationBarParentFingerprint, RenderBottomNavigationBarParentFingerprint, SetPivotBarVisibilityParentFingerprint, ), @@ -95,29 +84,30 @@ object HideShortsComponentsPatch : BytecodePatch( // region Hide the Shorts shelf. // This patch point is not present in 19.03.x and greater. - // If 19.02.x and lower is dropped, then this section of code and the fingerprint should be removed. - ReelConstructorFingerprint.result?.let { - it.mutableMethod.apply { - val insertIndex = it.scanResult.patternScanResult!!.startIndex + 2 - val viewRegister = getInstruction(insertIndex).registerA - - injectHideViewCall( - insertIndex, - viewRegister, - FILTER_CLASS_DESCRIPTOR, - "hideShortsShelf", - ) + if (!VersionCheckPatch.is_19_03_or_greater) { + ReelConstructorFingerprint.result?.let { + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.startIndex + 2 + val viewRegister = getInstruction(insertIndex).registerA + + injectHideViewCall( + insertIndex, + viewRegister, + FILTER_CLASS_DESCRIPTOR, + "hideShortsShelf", + ) + } } - } // Do not throw an exception if not resolved. + } // endregion // region Hide the Shorts buttons in older versions of YouTube. // Some Shorts buttons are views, hide them by setting their visibility to GONE. - CreateShortsButtonsFingerprint.result?.let { + CreateShortsButtonsFingerprint.resultOrThrow().let { ShortsButtons.entries.forEach { button -> button.injectHideCall(it.mutableMethod) } - } ?: throw CreateShortsButtonsFingerprint.exception + } // endregion @@ -125,54 +115,68 @@ object HideShortsComponentsPatch : BytecodePatch( LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR) - // endregion + context.forEachLiteralValueInstruction( + HideShortsComponentsResourcePatch.reelPlayerRightPivotV2Size + ) { literalInstructionIndex -> + val targetIndex = indexOfFirstInstructionOrThrow(literalInstructionIndex) { + getReference()?.name == "getDimensionPixelSize" + } + 1 - // region Hide the navigation bar. + val sizeRegister = getInstruction(targetIndex).registerA - // Hook to get the pivotBar view. - SetPivotBarVisibilityParentFingerprint.result?.let { - if (!SetPivotBarVisibilityFingerprint.resolve(context, it.classDef)) { - throw SetPivotBarVisibilityFingerprint.exception - } + addInstructions(targetIndex + 1, """ + invoke-static { v$sizeRegister }, $FILTER_CLASS_DESCRIPTOR->getSoundButtonSize(I)I + move-result v$sizeRegister + """ + ) + } - SetPivotBarVisibilityFingerprint.result!!.let { result -> - result.mutableMethod.apply { - val insertIndex = result.scanResult.patternScanResult!!.endIndex - val viewRegister = getInstruction(insertIndex - 1).registerA - addInstruction( - insertIndex, - "sput-object v$viewRegister, $FILTER_CLASS_DESCRIPTOR->pivotBar:" + - "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;", - ) - } - } - } ?: throw SetPivotBarVisibilityParentFingerprint.exception + // endregion - // Hook to hide the navigation bar when Shorts are being played. - RenderBottomNavigationBarParentFingerprint.result?.let { - if (!RenderBottomNavigationBarFingerprint.resolve(context, it.classDef)) { - throw RenderBottomNavigationBarFingerprint.exception - } + // region Hide the navigation bar. - RenderBottomNavigationBarFingerprint.result!!.mutableMethod.apply { - addInstruction(0, "invoke-static { }, $FILTER_CLASS_DESCRIPTOR->hideNavigationBar()V") - } - } ?: throw RenderBottomNavigationBarParentFingerprint.exception - - // Required to prevent a black bar from appearing at the bottom of the screen. - BottomNavigationBarFingerprint.result?.let { - it.mutableMethod.apply { - val moveResultIndex = it.scanResult.patternScanResult!!.startIndex + 2 - val viewRegister = getInstruction(moveResultIndex).registerA - val insertIndex = moveResultIndex + 1 - - addInstruction( - insertIndex, - "invoke-static { v$viewRegister }, $FILTER_CLASS_DESCRIPTOR->" + - "hideNavigationBar(Landroid/view/View;)Landroid/view/View;", + // Hook to get the pivotBar view. + SetPivotBarVisibilityFingerprint.alsoResolve( + context, + SetPivotBarVisibilityParentFingerprint + ).let { result-> + result.mutableMethod.apply { + val insertIndex = result.scanResult.patternScanResult!!.endIndex + val viewRegister = getInstruction(insertIndex - 1).registerA + addInstruction(insertIndex, "invoke-static {v$viewRegister}," + + " $FILTER_CLASS_DESCRIPTOR->setNavigationBar(Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;)V" ) } - } ?: throw BottomNavigationBarFingerprint.exception + } + + // Hook to hide the shared navigation bar when the Shorts player is opened. + RenderBottomNavigationBarFingerprint.alsoResolve( + context, + if (VersionCheckPatch.is_19_41_or_greater) + RenderBottomNavigationBarParentFingerprint + else + LegacyRenderBottomNavigationBarParentFingerprint + ).mutableMethod.addInstruction( + 0, + "invoke-static { p1 }, $FILTER_CLASS_DESCRIPTOR->hideNavigationBar(Ljava/lang/String;)V" + ) + + // Hide the bottom bar container of the Shorts player. + ShortsBottomBarContainerFingerprint.resultOrThrow().mutableMethod.apply { + val resourceIndex = indexOfFirstWideLiteralInstructionValue(HideShortsComponentsResourcePatch.bottomBarContainer) + + val targetIndex = indexOfFirstInstructionOrThrow(resourceIndex) { + getReference()?.name == "getHeight" + } + 1 + + val heightRegister = getInstruction(targetIndex).registerA + + addInstructions(targetIndex + 1, """ + invoke-static { v$heightRegister }, $FILTER_CLASS_DESCRIPTOR->getNavigationBarHeight(I)I + move-result v$heightRegister + """ + ) + } // endregion } @@ -187,14 +191,12 @@ object HideShortsComponentsPatch : BytecodePatch( fun injectHideCall(method: MutableMethod) { val referencedIndex = method.indexOfIdResourceOrThrow(resourceName) - val instruction = method.implementation!!.instructions - .subList(referencedIndex, referencedIndex + 20) - .first { - it.opcode == Opcode.INVOKE_VIRTUAL && it.getReference()?.name == "setId" - } + val setIdIndex = method.indexOfFirstInstructionOrThrow(referencedIndex) { + opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "setId" + } - val setIdIndex = instruction.location.index val viewRegister = method.getInstruction(setIdIndex).registerC + method.injectHideViewCall(setIdIndex + 1, viewRegister, FILTER_CLASS_DESCRIPTOR, methodName) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt index 866d8fb19b..38a7e99eba 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt @@ -8,13 +8,23 @@ import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsAppShortcut import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsWidget +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.util.findElementByAttributeValueOrThrow -@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class]) +@Patch( + dependencies = [ + SettingsPatch::class, + ResourceMappingPatch::class, + AddResourcesPatch::class, + VersionCheckPatch::class + ] +) object HideShortsComponentsResourcePatch : ResourcePatch() { internal var reelMultipleItemShelfId = -1L internal var reelPlayerRightCellButtonHeight = -1L + internal var bottomBarContainer = -1L + internal var reelPlayerRightPivotV2Size = -1L override fun execute(context: ResourceContext) { AddResourcesPatch(this::class) @@ -86,14 +96,21 @@ object HideShortsComponentsResourcePatch : ResourcePatch() { "reel_player_right_cell_button_height", ] - // Resource not present in new versions of the app. - try { - ResourceMappingPatch[ + bottomBarContainer = ResourceMappingPatch[ + "id", + "bottom_bar_container" + ] + + reelPlayerRightPivotV2Size = ResourceMappingPatch[ + "dimen", + "reel_player_right_pivot_v2_size" + ] + + if (!VersionCheckPatch.is_19_03_or_greater) { + reelMultipleItemShelfId = ResourceMappingPatch[ "dimen", "reel_player_right_cell_button_height", ] - } catch (e: NoSuchElementException) { - return - }.also { reelPlayerRightCellButtonHeight = it } + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/BottomNavigationBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/BottomNavigationBarFingerprint.kt deleted file mode 100644 index 12ca03fb13..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/BottomNavigationBarFingerprint.kt +++ /dev/null @@ -1,24 +0,0 @@ -package app.revanced.patches.youtube.layout.hide.shorts.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 BottomNavigationBarFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Landroid/view/View;", "Landroid/os/Bundle;"), - opcodes = listOf( - Opcode.CONST, // R.id.app_engagement_panel_wrapper - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IF_EQZ, - Opcode.IGET_OBJECT, - Opcode.IGET_OBJECT, - Opcode.IGET_OBJECT, - ), - strings = listOf( - "ReelWatchPaneFragmentViewModelKey" - ), -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/LegacyRenderBottomNavigationBarParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/LegacyRenderBottomNavigationBarParentFingerprint.kt new file mode 100644 index 0000000000..de25cf8706 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/LegacyRenderBottomNavigationBarParentFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.layout.hide.shorts.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object LegacyRenderBottomNavigationBarParentFingerprint : MethodFingerprint( + parameters = listOf( + "I", + "I", + "L", + "L", + "J", + "L", + ), + strings = listOf("aa") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ReelConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ReelConstructorFingerprint.kt index dece9b3718..912aa9704c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ReelConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ReelConstructorFingerprint.kt @@ -1,19 +1,13 @@ package app.revanced.patches.youtube.layout.hide.shorts.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsResourcePatch -import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.patch.LiteralValueFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -internal object ReelConstructorFingerprint : MethodFingerprint( +internal object ReelConstructorFingerprint : LiteralValueFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, opcodes = listOf(Opcode.INVOKE_VIRTUAL), - customFingerprint = { methodDef, _ -> - // Cannot use LiteralValueFingerprint, because the resource id may not be present. - val reelMultipleItemShelfId = HideShortsComponentsResourcePatch.reelMultipleItemShelfId - reelMultipleItemShelfId != -1L - && methodDef.containsWideLiteralInstructionValue(reelMultipleItemShelfId) - } + literalSupplier = { HideShortsComponentsResourcePatch.reelMultipleItemShelfId } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarFingerprint.kt index d6d74b1e83..89a0ef942b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarFingerprint.kt @@ -4,6 +4,8 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.Opcode internal object RenderBottomNavigationBarFingerprint : MethodFingerprint( + returnType = "V", + parameters = listOf("Ljava/lang/String;"), opcodes = listOf( Opcode.IGET_OBJECT, Opcode.MONITOR_ENTER, diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarParentFingerprint.kt index cb606d5c31..00ee226b3d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarParentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/RenderBottomNavigationBarParentFingerprint.kt @@ -1,8 +1,23 @@ package app.revanced.patches.youtube.layout.hide.shorts.fingerprints +import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +/** + * Identical to [LegacyRenderBottomNavigationBarParentFingerprint] + * except this has an extra parameter. + */ internal object RenderBottomNavigationBarParentFingerprint : MethodFingerprint( - parameters = listOf("I", "I", "L", "L", "J", "L"), + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf( + "I", + "I", + "L", // ReelWatchEndpointOuterClass + "L", + "J", + "Ljava/lang/String;", + "L" + ), strings = listOf("aa") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ShortsBottomBarContainerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ShortsBottomBarContainerFingerprint.kt new file mode 100644 index 0000000000..ca9fadf590 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/fingerprints/ShortsBottomBarContainerFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.hide.shorts.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object ShortsBottomBarContainerFingerprint : LiteralValueFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/View;", "Landroid/os/Bundle;"), + strings = listOf( + "r_pfvc" + ), + literalSupplier = { HideShortsComponentsResourcePatch.bottomBarContainer } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/suggestedvideoendscreen/DisableSuggestedVideoEndScreenPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/suggestedvideoendscreen/DisableSuggestedVideoEndScreenPatch.kt index aee5ad2884..ec2802ed21 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/suggestedvideoendscreen/DisableSuggestedVideoEndScreenPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/suggestedvideoendscreen/DisableSuggestedVideoEndScreenPatch.kt @@ -18,29 +18,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt index 1fee6435e9..47b89e353c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt @@ -19,29 +19,11 @@ import app.revanced.util.exception compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt index 4d7c52a48f..58f80a72d2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt @@ -1,17 +1,20 @@ +@file:Suppress("SpellCheckingInspection") + package app.revanced.patches.youtube.layout.miniplayer import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.all.misc.resources.AddResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen @@ -26,6 +29,7 @@ import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.sc import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.ytOutlinePictureInPictureWhite24 import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.ytOutlineXWhite24 import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerDimensionsCalculatorParentFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerMinimumSizeFingerprint import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernAddViewListenerFingerprint import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernCloseButtonFingerprint import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernConstructorFingerprint @@ -42,30 +46,32 @@ import app.revanced.patches.youtube.layout.miniplayer.fingerprints.YouTubePlayer import app.revanced.patches.youtube.layout.miniplayer.fingerprints.YouTubePlayerOverlaysLayoutFingerprint.YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME import app.revanced.patches.youtube.layout.tablet.fingerprints.GetFormFactorFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch +import app.revanced.util.addInstructionsAtControlFlowLabel +import app.revanced.util.alsoResolve import app.revanced.util.findOpcodeIndicesReversed import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow -import app.revanced.util.patch.LiteralValueFingerprint 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.MutableMethodImplementation import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.TypeReference import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter -// YT uses "Miniplayer" without a space between 'mini' and 'player: https://support.google.com/youtube/answer/9162927. +// YT uses "Miniplayer" without a space between 'mini' and 'player': https://support.google.com/youtube/answer/9162927. @Patch( name = "Miniplayer", - description = "Adds options to change the in app minimized player, " + - "and if patching target 19.16+ adds options to use modern miniplayers.", + description = "Adds options to change the in app minimized player. " + + "Patching target 19.16+ adds modern miniplayers.", dependencies = [ IntegrationsPatch::class, SettingsPatch::class, @@ -75,32 +81,28 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - // 19.14 is left out, as it has incomplete miniplayer code and missing some UI resources. - // It's simpler to not bother with supporting this single old version. - // 19.15 has a different code for handling sub title texts, - // and also probably not worth making changes just to support this single old version. - "19.16.39" // Earliest supported version with modern miniplayers. + // 19.14.43 // Incomplete code for modern miniplayers. + // 19.15.36 // Different code for handling subtitle texts and not worth supporting. + "19.16.39", // First with modern miniplayers. + // 19.17.41 // Works without issues, but no reason to recommend over 19.16. + // 19.18.41 // Works without issues, but no reason to recommend over 19.16. + // 19.19.39 // Last bug free version with smaller Modern 1 miniplayer, but no reason to recommend over 19.16. + // 19.20.35 // Cannot swipe to expand. + // 19.21.40 // Cannot swipe to expand. + // 19.22.43 // Cannot swipe to expand. + // 19.23.40 // First with Modern 1 drag and drop, Cannot swipe to expand. + // 19.24.45 // First with larger Modern 1, Cannot swipe to expand. + "19.25.37", // First with double tap, last with skip forward/back buttons, last with swipe to expand/close, and last before double tap to expand seems to be required. + // 19.26.42 // Modern 1 Pause/play button are always hidden. Unusable. + // 19.28.42 // First with custom miniplayer size, screen flickers when swiping to maximize Modern 1. Swipe to close miniplayer is broken. + // 19.29.42 // All modern players are broken and ignore tapping the miniplayer video. + // 19.30.39 // Modern 3 is less broken when double tap expand is enabled, but cannot swipe to expand when double tap is off. + // 19.31.36 // All Modern 1 buttons are missing. Unusable. + // 19.32.36 // 19.32+ and beyond all work without issues. + // 19.33.35 + "19.34.42", ] ) ] @@ -113,6 +115,7 @@ object MiniplayerPatch : BytecodePatch( MiniplayerOverrideFingerprint, MiniplayerModernConstructorFingerprint, MiniplayerModernViewParentFingerprint, + MiniplayerMinimumSizeFingerprint, YouTubePlayerOverlaysLayoutFingerprint ) ) { @@ -121,48 +124,71 @@ object MiniplayerPatch : BytecodePatch( override fun execute(context: BytecodeContext) { AddResourcesPatch(this::class) - // Modern mini player is only present and functional in 19.15+. - // Resource is not present in older versions. Using it to determine, if patching an old version. - val isPatchingOldVersion = ytOutlinePictureInPictureWhite24 < 0 + val preferences = mutableSetOf() + + if (!VersionCheckPatch.is_19_16_or_greater) { + preferences += ListPreference( + "revanced_miniplayer_type", + summaryKey = null, + entriesKey = "revanced_miniplayer_type_legacy_entries", + entryValuesKey = "revanced_miniplayer_type_legacy_entry_values" + ) + } else { + preferences += ListPreference( + "revanced_miniplayer_type", + summaryKey = null, + ) + + if (VersionCheckPatch.is_19_25_or_greater) { + if (!VersionCheckPatch.is_19_29_or_greater) { + preferences += SwitchPreference("revanced_miniplayer_double_tap_action") + } + preferences += SwitchPreference("revanced_miniplayer_drag_and_drop") + } + + if (VersionCheckPatch.is_19_36_or_greater) { + preferences += SwitchPreference("revanced_miniplayer_rounded_corners") + } + + preferences += SwitchPreference("revanced_miniplayer_hide_subtext") + + preferences += + if (VersionCheckPatch.is_19_26_or_greater) { + SwitchPreference("revanced_miniplayer_hide_expand_close") + } else { + SwitchPreference( + key = "revanced_miniplayer_hide_expand_close", + titleKey = "revanced_miniplayer_hide_expand_close_legacy_title", + summaryOnKey = "revanced_miniplayer_hide_expand_close_legacy_summary_on", + summaryOffKey = "revanced_miniplayer_hide_expand_close_legacy_summary_off", + ) + } + + if (!VersionCheckPatch.is_19_26_or_greater) { + preferences += SwitchPreference("revanced_miniplayer_hide_rewind_forward") + } + + if (VersionCheckPatch.is_19_26_or_greater) { + preferences += TextPreference("revanced_miniplayer_width_dip", inputType = InputType.NUMBER) + } + + preferences += TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER) + } SettingsPatch.PreferenceScreen.PLAYER.addPreferences( PreferenceScreen( key = "revanced_miniplayer_screen", sorting = Sorting.UNSORTED, - preferences = - if (isPatchingOldVersion) { - setOf( - ListPreference( - "revanced_miniplayer_type", - summaryKey = null, - entriesKey = "revanced_miniplayer_type_legacy_entries", - entryValuesKey = "revanced_miniplayer_type_legacy_entry_values" - ) - ) - } else { - setOf( - ListPreference( - "revanced_miniplayer_type", - summaryKey = null, - entriesKey = "revanced_miniplayer_type_19_15_entries", - entryValuesKey = "revanced_miniplayer_type_19_15_entry_values" - ), - SwitchPreference("revanced_miniplayer_hide_expand_close"), - SwitchPreference("revanced_miniplayer_hide_subtext"), - SwitchPreference("revanced_miniplayer_hide_rewind_forward"), - TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER) - ) - } + preferences = preferences ) ) // region Enable tablet miniplayer. - MiniplayerOverrideNoContextFingerprint.resolve( + MiniplayerOverrideNoContextFingerprint.alsoResolve( context, - MiniplayerDimensionsCalculatorParentFingerprint.resultOrThrow().classDef - ) - MiniplayerOverrideNoContextFingerprint.resultOrThrow().mutableMethod.apply { + MiniplayerDimensionsCalculatorParentFingerprint + ).mutableMethod.apply { findReturnIndicesReversed().forEach { index -> insertLegacyTabletMiniplayerOverride(index) } } @@ -188,8 +214,8 @@ object MiniplayerPatch : BytecodePatch( it.mutableMethod.insertLegacyTabletMiniplayerOverride(it.scanResult.patternScanResult!!.endIndex) } - if (isPatchingOldVersion) { - // Return here, as patch below is only intended for new versions of the app. + if (!VersionCheckPatch.is_19_16_or_greater) { + // Return here, as patch below is only for the current versions of the app. return } @@ -212,59 +238,150 @@ object MiniplayerPatch : BytecodePatch( } } + if (VersionCheckPatch.is_19_23_or_greater) { + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.DRAG_DROP_ENABLED_FEATURE_KEY_LITERAL, + "enableMiniplayerDragAndDrop" + ) + } + + if (VersionCheckPatch.is_19_25_or_greater) { + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.MODERN_MINIPLAYER_ENABLED_OLD_TARGETS_FEATURE_KEY, + "getModernMiniplayerOverride" + ) + + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.MODERN_FEATURE_FLAGS_ENABLED_KEY_LITERAL, + "getModernFeatureFlagsActiveOverride" + ) + + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.DOUBLE_TAP_ENABLED_FEATURE_KEY_LITERAL, + "enableMiniplayerDoubleTapAction" + ) + } + + if (VersionCheckPatch.is_19_26_or_greater) { + MiniplayerModernConstructorFingerprint.resultOrThrow().mutableMethod.apply { + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow( + MiniplayerModernConstructorFingerprint.INITIAL_SIZE_FEATURE_KEY_LITERAL + ) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT) + + val register = getInstruction(targetIndex).registerA + + addInstructions( + targetIndex + 1, + """ + invoke-static { v$register }, $INTEGRATIONS_CLASS_DESCRIPTOR->setMiniplayerDefaultSize(I)I + move-result v$register + """ + ) + } + + // Override a mininimum miniplayer size constant. + MiniplayerMinimumSizeFingerprint.resultOrThrow().mutableMethod.apply { + val index = indexOfFirstInstructionOrThrow { + opcode == Opcode.CONST_16 && (this as NarrowLiteralInstruction).narrowLiteral == 192 + } + val register = getInstruction(index).registerA + + // Smaller sizes can be used, but the miniplayer will always start in size 170 if set any smaller. + // The 170 initial limit probably could be patched to allow even smaller initial sizes, + // but 170 is already half the horizontal space and smaller does not seem useful. + replaceInstruction(index, "const/16 v$register, 170") + } + } + + if (VersionCheckPatch.is_19_32_or_greater) { + // Feature is not exposed in the settings, and currently only for debugging. + + MiniplayerModernConstructorFingerprint.insertLiteralValueFloatOverride( + MiniplayerModernConstructorFingerprint.ANIMATION_INTERPOLATION_FEATURE_KEY, + "setMovementBoundFactor" + ) + } + + if (VersionCheckPatch.is_19_36_or_greater) { + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.DROP_SHADOW_FEATURE_KEY, + "setDropShadow" + ) + + MiniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride( + MiniplayerModernConstructorFingerprint.ROUNDED_CORNERS_FEATURE_KEY, + "setRoundedCorners" + ) + } + // endregion // region Fix 19.16 using mixed up drawables for tablet modern. // YT fixed this mistake in 19.17. // Fix this, by swapping the drawable resource values with each other. - - MiniplayerModernExpandCloseDrawablesFingerprint.apply { - resolve( + if (ytOutlinePictureInPictureWhite24 >= 0) { + MiniplayerModernExpandCloseDrawablesFingerprint.alsoResolve( context, - MiniplayerModernViewParentFingerprint.resultOrThrow().classDef - ) - }.resultOrThrow().mutableMethod.apply { - listOf( - ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24, - ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24, - ).forEach { (originalResource, replacementResource) -> - val imageResourceIndex = indexOfFirstWideLiteralInstructionValueOrThrow(originalResource) - val register = getInstruction(imageResourceIndex).registerA - - replaceInstruction(imageResourceIndex, "const v$register, $replacementResource") + MiniplayerModernViewParentFingerprint + ).mutableMethod.apply { + listOf( + ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24, + ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24, + ).forEach { (originalResource, replacementResource) -> + val imageResourceIndex = indexOfFirstWideLiteralInstructionValueOrThrow(originalResource) + val register = getInstruction(imageResourceIndex).registerA + + replaceInstruction(imageResourceIndex, "const v$register, $replacementResource") + } } } // endregion - - // region Add hooks to hide tablet modern miniplayer buttons. + // region Add hooks to hide modern miniplayer buttons. listOf( - Triple(MiniplayerModernExpandButtonFingerprint, modernMiniplayerExpand,"hideMiniplayerExpandClose"), - Triple(MiniplayerModernCloseButtonFingerprint, modernMiniplayerClose, "hideMiniplayerExpandClose"), - Triple(MiniplayerModernRewindButtonFingerprint, modernMiniplayerRewindButton, "hideMiniplayerRewindForward"), - Triple(MiniplayerModernForwardButtonFingerprint, modernMiniplayerForwardButton, "hideMiniplayerRewindForward"), - Triple(MiniplayerModernOverlayViewFingerprint, scrimOverlay, "adjustMiniplayerOpacity") + Triple( + MiniplayerModernExpandButtonFingerprint, + modernMiniplayerExpand, + "hideMiniplayerExpandClose" + ), + Triple( + MiniplayerModernCloseButtonFingerprint, + modernMiniplayerClose, + "hideMiniplayerExpandClose" + ), + Triple( + MiniplayerModernRewindButtonFingerprint, + modernMiniplayerRewindButton, + "hideMiniplayerRewindForward" + ), + Triple( + MiniplayerModernForwardButtonFingerprint, + modernMiniplayerForwardButton, + "hideMiniplayerRewindForward" + ), + Triple( + MiniplayerModernOverlayViewFingerprint, + scrimOverlay, + "adjustMiniplayerOpacity" + ) ).forEach { (fingerprint, literalValue, methodName) -> - fingerprint.resolve( + fingerprint.alsoResolve( context, - MiniplayerModernViewParentFingerprint.resultOrThrow().classDef - ) - - fingerprint.hookInflatedView( + MiniplayerModernViewParentFingerprint + ).mutableMethod.hookInflatedView( literalValue, "Landroid/widget/ImageView;", "$INTEGRATIONS_CLASS_DESCRIPTOR->$methodName(Landroid/widget/ImageView;)V" ) } - MiniplayerModernAddViewListenerFingerprint.apply { - resolve( - context, - MiniplayerModernViewParentFingerprint.resultOrThrow().classDef - ) - }.resultOrThrow().mutableMethod.addInstruction( + MiniplayerModernAddViewListenerFingerprint.alsoResolve( + context, + MiniplayerModernViewParentFingerprint + ).mutableMethod.addInstruction( 0, "invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->" + "hideMiniplayerSubTexts(Landroid/view/View;)V" @@ -275,6 +392,9 @@ object MiniplayerPatch : BytecodePatch( // Modern 2 uses the same overlay controls as the regular video player, // and the overlay views are added at runtime. // Add a hook to the overlay class, and pass the added views to integrations. + // + // NOTE: Modern 2 uses the same video UI as the regular player except resized to smaller. + // This patch code could be used to hide other player overlays that do not use Litho. YouTubePlayerOverlaysLayoutFingerprint.resultOrThrow().mutableClass.methods.add( ImmutableMethod( YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME, @@ -319,6 +439,37 @@ object MiniplayerPatch : BytecodePatch( insertBooleanOverride(index, "getModernMiniplayerOverride") } + private fun MethodFingerprint.insertLiteralValueBooleanOverride( + literal: Long, + integrationsMethod: String + ) { + resultOrThrow().mutableMethod.apply { + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) + + insertBooleanOverride(targetIndex + 1, integrationsMethod) + } + } + + private fun MethodFingerprint.insertLiteralValueFloatOverride( + literal: Long, + integrationsMethod: String + ) { + resultOrThrow().mutableMethod.apply { + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT) + val register = getInstruction(targetIndex).registerA + + addInstructions( + targetIndex + 1, + """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->$integrationsMethod(F)F + move-result v$register + """ + ) + } + } + private fun MutableMethod.insertBooleanOverride(index: Int, methodName: String) { val register = getInstruction(index).registerA addInstructions( @@ -335,36 +486,30 @@ object MiniplayerPatch : BytecodePatch( */ private fun MutableMethod.insertModernMiniplayerTypeOverride(iPutIndex: Int) { val targetInstruction = getInstruction(iPutIndex) - val targetReference = (targetInstruction as ReferenceInstruction).reference - addInstructions( - iPutIndex + 1, """ + addInstructionsAtControlFlowLabel( + iPutIndex, """ invoke-static { v${targetInstruction.registerA} }, $INTEGRATIONS_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I move-result v${targetInstruction.registerA} - # Original instruction - iput v${targetInstruction.registerA}, v${targetInstruction.registerB}, $targetReference """ ) - removeInstruction(iPutIndex) } - private fun LiteralValueFingerprint.hookInflatedView( + private fun MutableMethod.hookInflatedView( literalValue: Long, hookedClassType: String, integrationsMethodName: String, ) { - resultOrThrow().mutableMethod.apply { - val imageViewIndex = indexOfFirstInstructionOrThrow( - indexOfFirstWideLiteralInstructionValueOrThrow(literalValue) - ) { - opcode == Opcode.CHECK_CAST && getReference()?.type == hookedClassType - } - - val register = getInstruction(imageViewIndex).registerA - addInstruction( - imageViewIndex + 1, - "invoke-static { v$register }, $integrationsMethodName" - ) + val imageViewIndex = indexOfFirstInstructionOrThrow( + indexOfFirstWideLiteralInstructionValueOrThrow(literalValue) + ) { + opcode == Opcode.CHECK_CAST && getReference()?.type == hookedClassType } + + val register = getInstruction(imageViewIndex).registerA + addInstruction( + imageViewIndex + 1, + "invoke-static { v$register }, $integrationsMethodName" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerResourcePatch.kt index 3870f06544..1c8649c097 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerResourcePatch.kt @@ -1,12 +1,12 @@ package app.revanced.patches.youtube.layout.miniplayer import app.revanced.patcher.data.ResourceContext -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch -@Patch(dependencies = [ResourceMappingPatch::class]) +@Patch(dependencies = [ResourceMappingPatch::class, VersionCheckPatch::class]) internal object MiniplayerResourcePatch : ResourcePatch() { var floatyBarButtonTopMargin = -1L @@ -19,6 +19,7 @@ internal object MiniplayerResourcePatch : ResourcePatch() { var modernMiniplayerRewindButton = -1L var modernMiniplayerForwardButton = -1L var playerOverlays = -1L + var miniplayerMaxSize = -1L override fun execute(context: ResourceContext) { floatyBarButtonTopMargin = ResourceMappingPatch[ @@ -26,56 +27,63 @@ internal object MiniplayerResourcePatch : ResourcePatch() { "floaty_bar_button_top_margin" ] - try { - ytOutlinePictureInPictureWhite24 = ResourceMappingPatch[ - "drawable", - "yt_outline_picture_in_picture_white_24" - ] - } catch (exception: PatchException) { - // Ignore, and assume the app is 19.14 or earlier. - return - } - - ytOutlineXWhite24 = ResourceMappingPatch[ - "drawable", - "yt_outline_x_white_24" - ] - scrimOverlay = ResourceMappingPatch[ "id", "scrim_overlay" ] - modernMiniplayerClose = ResourceMappingPatch[ - "id", - "modern_miniplayer_close" + playerOverlays = ResourceMappingPatch[ + "layout", + "player_overlays" ] - modernMiniplayerExpand = ResourceMappingPatch[ - "id", - "modern_miniplayer_expand" - ] + if (VersionCheckPatch.is_19_16_or_greater) { + modernMiniplayerClose = ResourceMappingPatch[ + "id", + "modern_miniplayer_close" + ] - modernMiniplayerRewindButton = ResourceMappingPatch[ - "id", - "modern_miniplayer_rewind_button" - ] + modernMiniplayerExpand = ResourceMappingPatch[ + "id", + "modern_miniplayer_expand" + ] - modernMiniplayerForwardButton = ResourceMappingPatch[ - "id", - "modern_miniplayer_forward_button" - ] + modernMiniplayerRewindButton = ResourceMappingPatch[ + "id", + "modern_miniplayer_rewind_button" + ] - playerOverlays = ResourceMappingPatch[ - "layout", - "player_overlays" - ] + modernMiniplayerForwardButton = ResourceMappingPatch[ + "id", + "modern_miniplayer_forward_button" + ] - // Resource id is not used during patching, but is used by integrations. - // Verify the resource is present while patching. - ResourceMappingPatch[ - "id", - "modern_miniplayer_subtitle_text" - ] + // Resource id is not used during patching, but is used by integrations. + // Verify the resource is present while patching. + ResourceMappingPatch[ + "id", + "modern_miniplayer_subtitle_text" + ] + + // Only required for exactly 19.16 + if (!VersionCheckPatch.is_19_17_or_greater) { + ytOutlinePictureInPictureWhite24 = ResourceMappingPatch[ + "drawable", + "yt_outline_picture_in_picture_white_24" + ] + + ytOutlineXWhite24 = ResourceMappingPatch[ + "drawable", + "yt_outline_x_white_24" + ] + } + + if (VersionCheckPatch.is_19_26_or_greater) { + miniplayerMaxSize = ResourceMappingPatch[ + "dimen", + "miniplayer_max_size" + ] + } + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerMinimumSizeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerMinimumSizeFingerprint.kt new file mode 100644 index 0000000000..134eaed429 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerMinimumSizeFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.containsWideLiteralInstructionValue +import com.android.tools.smali.dexlib2.AccessFlags + +internal object MiniplayerMinimumSizeFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(192) + && methodDef.containsWideLiteralInstructionValue(128) + && methodDef.containsWideLiteralInstructionValue(MiniplayerResourcePatch.miniplayerMaxSize) + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt index 0afb5e5264..1b0c7096e2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt @@ -8,4 +8,14 @@ internal object MiniplayerModernConstructorFingerprint : LiteralValueFingerprint accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, parameters = listOf("L"), literalSupplier = { 45623000L } // Magic number found in the constructor. -) \ No newline at end of file +) { + const val MODERN_FEATURE_FLAGS_ENABLED_KEY_LITERAL = 45622882L + // In later targets this feature flag does nothing and is dead code. + const val MODERN_MINIPLAYER_ENABLED_OLD_TARGETS_FEATURE_KEY = 45630429L + const val DOUBLE_TAP_ENABLED_FEATURE_KEY_LITERAL = 45628823L + const val DRAG_DROP_ENABLED_FEATURE_KEY_LITERAL = 45628752L + const val INITIAL_SIZE_FEATURE_KEY_LITERAL = 45640023L + const val ANIMATION_INTERPOLATION_FEATURE_KEY = 45647018L + const val DROP_SHADOW_FEATURE_KEY = 45652223L + const val ROUNDED_CORNERS_FEATURE_KEY = 45652224L +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt index 9d9bf5e153..3a1fbfb43b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt @@ -7,6 +7,5 @@ import com.android.tools.smali.dexlib2.AccessFlags internal object MiniplayerOverrideFingerprint : MethodFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("L"), strings = listOf("appName") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch.kt index c93d718f5b..5675204b13 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch.kt @@ -19,30 +19,11 @@ import app.revanced.util.exception compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/player/background/PlayerControlsBackgroundPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/player/background/PlayerControlsBackgroundPatch.kt index eec03ff01f..f67bdc9f98 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/player/background/PlayerControlsBackgroundPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/player/background/PlayerControlsBackgroundPatch.kt @@ -14,30 +14,11 @@ import org.w3c.dom.Element CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index 8e4556a008..9485bc3d24 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstructions import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException @@ -27,11 +28,14 @@ import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.Tex import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.shared.fingerprints.RollingNumberTextViewAnimationUpdateFingerprint 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 +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -51,27 +55,15 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference VideoIdPatch::class, ReturnYouTubeDislikeResourcePatch::class, PlayerTypeHookPatch::class, + VersionCheckPatch::class ], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] @@ -117,7 +109,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( DislikeFingerprint.toPatch(Vote.DISLIKE), RemoveLikeFingerprint.toPatch(Vote.REMOVE_LIKE) ).forEach { (fingerprint, vote) -> - fingerprint.result?.mutableMethod?.apply { + fingerprint.resultOrThrow().mutableMethod.apply { addInstructions( 0, """ @@ -125,7 +117,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->sendVote(I)V """ ) - } ?: throw fingerprint.exception + } } // endregion @@ -136,7 +128,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( // And it works in all situations except it fails to update the Span when the user dislikes, // since the underlying (likes only) text did not change. // This hook handles all situations, as it's where the created Spans are stored and later reused. - TextComponentConstructorFingerprint.result?.let { textConstructorResult -> + TextComponentConstructorFingerprint.resultOrThrow().let { textConstructorResult -> // Find the field name of the conversion context. val conversionContextClassType = ConversionContextFingerprint.resultOrThrow().classDef.type val conversionContextField = textConstructorResult.classDef.fields.find { @@ -147,55 +139,80 @@ object ReturnYouTubeDislikePatch : BytecodePatch( TextComponentLookupFingerprint.resultOrThrow().mutableMethod.apply { // Find the instruction for creating the text data object. val textDataClassType = TextComponentDataFingerprint.resultOrThrow().classDef.type - val insertIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.NEW_INSTANCE && - getReference()?.type == textDataClassType + + val insertIndex : Int + val tempRegister : Int + val charSequenceRegister : Int + + if (VersionCheckPatch.is_19_33_or_greater) { + insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC_RANGE && + getReference()?.returnType == textDataClassType + } + + tempRegister = getInstruction(insertIndex + 1).registerA + + // Find the instruction that sets the span to an instance field. + // The instruction is only a few lines after the creation of the instance. + charSequenceRegister = getInstruction( + indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.parameterTypes?.firstOrNull() == "Ljava/lang/CharSequence;" + } + ).registerD + } else { + insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.NEW_INSTANCE && + getReference()?.type == textDataClassType + } + + tempRegister = getInstruction(insertIndex).registerA + + charSequenceRegister = getInstruction( + indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Ljava/lang/CharSequence;" + } + ).registerA } - val tempRegister = getInstruction(insertIndex).registerA - - // Find the instruction that sets the span to an instance field. - // The instruction is only a few lines after the creation of the instance. - // The method has multiple iput-object instructions using a CharSequence, - // so verify the found instruction is in the expected location. - val putFieldInstruction = implementation!!.instructions - .subList(insertIndex, insertIndex + 20) - .find { - it.opcode == Opcode.IPUT_OBJECT && - it.getReference()?.type == "Ljava/lang/CharSequence;" - } ?: throw PatchException("Could not find put object instruction") - val charSequenceRegister = (putFieldInstruction as TwoRegisterInstruction).registerA - addInstructions( - insertIndex, + addInstructionsAtControlFlowLabel(insertIndex, + """ + # Copy conversion context + move-object/from16 v$tempRegister, p0 + iget-object v$tempRegister, v$tempRegister, $conversionContextField + invoke-static { v$tempRegister, v$charSequenceRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + move-result-object v$charSequenceRegister """ - # Copy conversion context - move-object/from16 v$tempRegister, p0 - iget-object v$tempRegister, v$tempRegister, $conversionContextField - invoke-static {v$tempRegister, v$charSequenceRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; - move-result-object v$charSequenceRegister - """ ) } - } ?: throw TextComponentConstructorFingerprint.exception + } // endregion // region Hook for non-litho Short videos. - ShortsTextViewFingerprint.result?.let { + ShortsTextViewFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val patternResult = it.scanResult.patternScanResult!! + val insertIndex = it.scanResult.patternScanResult!!.endIndex + 1 // If the field is true, the TextView is for a dislike button. - val isDisLikesBooleanReference = getInstruction(patternResult.endIndex).reference + val isDisLikesBooleanInstruction = getInstructions().first { instruction -> + instruction.opcode == Opcode.IGET_BOOLEAN + } as ReferenceInstruction + + val isDisLikesBooleanReference = isDisLikesBooleanInstruction.reference - val textViewFieldReference = // Like/Dislike button TextView field - getInstruction(patternResult.endIndex - 1).reference + // Like/Dislike button TextView field. + val textViewFieldInstruction = getInstructions().first { instruction -> + instruction.opcode == Opcode.IGET_OBJECT + } as ReferenceInstruction + + val textViewFieldReference = textViewFieldInstruction.reference // Check if the hooked TextView object is that of the dislike button. // If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted. // Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward. - val insertIndex = patternResult.startIndex + 6 addInstructionsWithLabels( insertIndex, """ @@ -216,7 +233,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( """ ) } - } ?: throw ShortsTextViewFingerprint.exception + } // endregion @@ -251,20 +268,14 @@ object ReturnYouTubeDislikePatch : BytecodePatch( // region Hook rolling numbers. - // Do this last to allow patching old unsupported versions (if the user really wants), - // On older unsupported version this will fail to resolve and throw an exception, - // but everything will still work correctly anyways. - - RollingNumberSetterFingerprint.result?.let { + RollingNumberSetterFingerprint.resultOrThrow().let { val dislikesIndex = it.scanResult.patternScanResult!!.endIndex it.mutableMethod.apply { val insertIndex = 1 - val charSequenceInstanceRegister = - getInstruction(0).registerA - val charSequenceFieldReference = - getInstruction(dislikesIndex).reference + val charSequenceInstanceRegister = getInstruction(0).registerA + val charSequenceFieldReference = getInstruction(dislikesIndex).reference val registerCount = implementation!!.registerCount @@ -282,7 +293,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( """ ) } - } ?: throw RollingNumberSetterFingerprint.exception + } // Rolling Number text views use the measured width of the raw string for layout. // Modify the measure text calculation to include the left drawable separator if needed. @@ -306,9 +317,12 @@ object ReturnYouTubeDislikePatch : BytecodePatch( // Additional text measurement method. Used if YouTube decides not to animate the likes count // and sometimes used for initial video load. - RollingNumberMeasureStaticLabelFingerprint.resolve(context, RollingNumberMeasureStaticLabelParentFingerprint.resultOrThrow().classDef) - RollingNumberMeasureStaticLabelFingerprint.result?.also { + RollingNumberMeasureStaticLabelFingerprint.alsoResolve( + context, + RollingNumberMeasureStaticLabelParentFingerprint + ).let { val measureTextIndex = it.scanResult.patternScanResult!!.startIndex + 1 + it.mutableMethod.apply { val freeRegister = getInstruction(0).registerA @@ -320,19 +334,18 @@ object ReturnYouTubeDislikePatch : BytecodePatch( """ ) } - } ?: throw RollingNumberMeasureStaticLabelFingerprint.exception + } // The rolling number Span is missing styling since it's initially set as a String. // Modify the UI text view and use the styled like/dislike Span. - RollingNumberTextViewFingerprint.result?.let { + RollingNumberTextViewFingerprint.resultOrThrow().let { // Initial TextView is set in this method. val initiallyCreatedTextViewMethod = it.mutableMethod // Videos less than 24 hours after uploaded, like counts will be updated in real time. // Whenever like counts are updated, TextView is set in this method. val realTimeUpdateTextViewMethod = - RollingNumberTextViewAnimationUpdateFingerprint.result?.mutableMethod - ?: throw RollingNumberTextViewAnimationUpdateFingerprint.exception + RollingNumberTextViewAnimationUpdateFingerprint.resultOrThrow().mutableMethod arrayOf( initiallyCreatedTextViewMethod, @@ -357,7 +370,7 @@ object ReturnYouTubeDislikePatch : BytecodePatch( ) } } - } ?: throw RollingNumberTextViewFingerprint.exception + } // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt index 75b6eb55cc..92ff788295 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ConversionContextFingerprint.kt @@ -10,9 +10,6 @@ internal object ConversionContextFingerprint : MethodFingerprint( ", heightConstraint=", ", templateLoggerFactory=", ", rootDisposableContainer=", - // 18.37.36 and after this String is: ConversionContext{containerInternal= - // and before it is: ConversionContext{container= - // Use a partial string to match both. - "ConversionContext{container" + "ConversionContext{containerInternal=" ) ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt index e79cf27292..57569b2709 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberSetterFingerprint.kt @@ -8,5 +8,6 @@ internal object RollingNumberSetterFingerprint : MethodFingerprint( Opcode.INVOKE_DIRECT, Opcode.IGET_OBJECT ), - strings = listOf("RollingNumberType required properties missing! Need updateCount, fontName, color and fontSize.") + // Partial string match. + strings = listOf("RollingNumberType required properties missing! Need") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt index 54fe00865f..ba109ac54a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RollingNumberTextViewFingerprint.kt @@ -18,6 +18,7 @@ internal object RollingNumberTextViewFingerprint : MethodFingerprint( Opcode.RETURN_VOID ), customFingerprint = { _, classDef -> - classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" + classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" || + classDef.superclass == "Lcom/google/android/libraries/youtube/rendering/ui/spec/typography/YouTubeAppCompatTextView;" } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt index a326a6ced8..75ec556068 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/ShortsTextViewFingerprint.kt @@ -15,18 +15,6 @@ internal object ShortsTextViewFingerprint : MethodFingerprint( null, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT_OBJECT, - Opcode.CHECK_CAST, - Opcode.SGET_OBJECT, // insertion point, must be after constructor call to parent class - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.CONST_4, - Opcode.IF_EQZ, - Opcode.CONST_4, - Opcode.IF_EQ, - Opcode.CONST_4, - Opcode.IF_EQ, - Opcode.RETURN_VOID, - Opcode.IGET_OBJECT, // TextView field - Opcode.IGET_BOOLEAN, // boolean field + Opcode.CHECK_CAST ) ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt index 2d70664030..6126fda655 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentDataFingerprint.kt @@ -9,8 +9,6 @@ internal object TextComponentDataFingerprint : MethodFingerprint( parameters = listOf("L", "L"), strings = listOf("text"), customFingerprint = { _, classDef -> - val fields = classDef.fields - fields.find { it.type == "Ljava/util/BitSet;" } != null && - fields.find { it.type == "[Ljava/lang/String;" } != null + classDef.fields.find { it.type == "Ljava/util/BitSet;" } != null } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/WideSearchbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/WideSearchbarPatch.kt index c8a75eb11c..cc029b92c0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/WideSearchbarPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/WideSearchbarPatch.kt @@ -24,29 +24,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/fingerprints/SetWordmarkHeaderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/fingerprints/SetWordmarkHeaderFingerprint.kt index bc5f29ff8e..0d4fa777e0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/fingerprints/SetWordmarkHeaderFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/searchbar/fingerprints/SetWordmarkHeaderFingerprint.kt @@ -18,6 +18,6 @@ internal object SetWordmarkHeaderFingerprint : MethodFingerprint( Opcode.IF_EQZ, Opcode.IGET_OBJECT, Opcode.CONST, - Opcode.INVOKE_STATIC, + null // invoke-static or invoke-virtual ) ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/RestoreOldSeekbarThumbnailsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/RestoreOldSeekbarThumbnailsPatch.kt index 3aecd97f07..c28aa2564b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/RestoreOldSeekbarThumbnailsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/RestoreOldSeekbarThumbnailsPatch.kt @@ -4,45 +4,28 @@ import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstructions import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.layout.seekbar.fingerprints.FullscreenSeekbarThumbnailsFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.util.exception @Patch( name = "Restore old seekbar thumbnails", description = "Adds an option to restore the old seekbar thumbnails that appear above the seekbar while seeking instead of fullscreen thumbnails.", - dependencies = [IntegrationsPatch::class, AddResourcesPatch::class], + dependencies = [IntegrationsPatch::class, AddResourcesPatch::class, VersionCheckPatch::class], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", - "19.16.39", + "19.16.39" + // 19.17+ is not supported. ] ) ] @@ -55,6 +38,11 @@ object RestoreOldSeekbarThumbnailsPatch : BytecodePatch( "Lapp/revanced/integrations/youtube/patches/RestoreOldSeekbarThumbnailsPatch;" override fun execute(context: BytecodeContext) { + if (VersionCheckPatch.is_19_17_or_greater) { + // Give a more informative error, if the user has turned off version checks. + throw PatchException("'Restore old seekbar thumbnails' cannot be patched to any version after 19.16.39") + } + AddResourcesPatch(this::class) SettingsPatch.PreferenceScreen.SEEKBAR.addPreferences( diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorBytecodePatch.kt index 08d3fa0274..8d3050c29f 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorBytecodePatch.kt @@ -1,20 +1,26 @@ package app.revanced.patches.youtube.layout.seekbar import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.layout.seekbar.fingerprints.LithoLinearGradientFingerprint import app.revanced.patches.youtube.layout.seekbar.fingerprints.PlayerSeekbarColorFingerprint +import app.revanced.patches.youtube.layout.seekbar.fingerprints.PlayerSeekbarGradientConfigFingerprint import app.revanced.patches.youtube.layout.seekbar.fingerprints.SetSeekbarClickedColorFingerprint import app.revanced.patches.youtube.layout.seekbar.fingerprints.ShortsSeekbarColorFingerprint import app.revanced.patches.youtube.layout.theme.LithoColorHookPatch import app.revanced.patches.youtube.layout.theme.LithoColorHookPatch.lithoColorOverrideHook import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch -import app.revanced.util.exception +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction @@ -24,7 +30,13 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction compatiblePackages = [CompatiblePackage("com.google.android.youtube")] ) internal object SeekbarColorBytecodePatch : BytecodePatch( - setOf(PlayerSeekbarColorFingerprint, ShortsSeekbarColorFingerprint, SetSeekbarClickedColorFingerprint) + setOf( + PlayerSeekbarColorFingerprint, + ShortsSeekbarColorFingerprint, + SetSeekbarClickedColorFingerprint, + PlayerSeekbarGradientConfigFingerprint, + LithoLinearGradientFingerprint + ) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/theme/SeekbarColorPatch;" @@ -32,25 +44,26 @@ internal object SeekbarColorBytecodePatch : BytecodePatch( fun MutableMethod.addColorChangeInstructions(resourceId: Long) { val registerIndex = indexOfFirstWideLiteralInstructionValueOrThrow(resourceId) + 2 val colorRegister = getInstruction(registerIndex).registerA + addInstructions( registerIndex + 1, """ - invoke-static { v$colorRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getVideoPlayerSeekbarColor(I)I - move-result v$colorRegister - """ + invoke-static { v$colorRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getVideoPlayerSeekbarColor(I)I + move-result v$colorRegister + """ ) } - PlayerSeekbarColorFingerprint.result?.mutableMethod?.apply { + PlayerSeekbarColorFingerprint.resultOrThrow().mutableMethod.apply { addColorChangeInstructions(SeekbarColorResourcePatch.inlineTimeBarColorizedBarPlayedColorDarkId) addColorChangeInstructions(SeekbarColorResourcePatch.inlineTimeBarPlayedNotHighlightedColorId) - } ?: throw PlayerSeekbarColorFingerprint.exception + } - ShortsSeekbarColorFingerprint.result?.mutableMethod?.apply { + ShortsSeekbarColorFingerprint.resultOrThrow().mutableMethod.apply { addColorChangeInstructions(SeekbarColorResourcePatch.reelTimeBarPlayedColorId) - } ?: throw ShortsSeekbarColorFingerprint.exception + } - SetSeekbarClickedColorFingerprint.result?.let { result -> + SetSeekbarClickedColorFingerprint.resultOrThrow().let { result -> result.mutableMethod.let { val setColorMethodIndex = result.scanResult.patternScanResult!!.startIndex + 1 val method = context @@ -69,7 +82,31 @@ internal object SeekbarColorBytecodePatch : BytecodePatch( ) } } - } ?: throw SetSeekbarClickedColorFingerprint.exception + } + + if (VersionCheckPatch.is_19_23_or_greater) { + PlayerSeekbarGradientConfigFingerprint.resultOrThrow().mutableMethod.apply { + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow( + PlayerSeekbarGradientConfigFingerprint.PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG + ) + val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) + val register = getInstruction(resultIndex).registerA + + addInstructions( + resultIndex + 1, + """ + invoke-static { v$register }, $INTEGRATIONS_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z + move-result v$register + """ + ) + } + + LithoLinearGradientFingerprint.resultOrThrow().mutableMethod.apply { + addInstruction(0, "invoke-static/range { p4 .. p5 }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->setLinearGradient([I[F)V" + ) + } + } lithoColorOverrideHook(INTEGRATIONS_CLASS_DESCRIPTOR, "getLithoColor") } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/LithoLinearGradientFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/LithoLinearGradientFingerprint.kt new file mode 100644 index 0000000000..6185420bf2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/LithoLinearGradientFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.seekbar.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object LithoLinearGradientFingerprint : MethodFingerprint( + accessFlags = AccessFlags.STATIC.value, + returnType = "Landroid/graphics/LinearGradient;", + parameters = listOf("F", "F", "F", "F", "[I", "[F"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/PlayerSeekbarGradientConfigFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/PlayerSeekbarGradientConfigFingerprint.kt new file mode 100644 index 0000000000..2d7730fb52 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/fingerprints/PlayerSeekbarGradientConfigFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.layout.seekbar.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.seekbar.fingerprints.PlayerSeekbarGradientConfigFingerprint.PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object PlayerSeekbarGradientConfigFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + parameters = listOf(), + literalSupplier = { PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG }, +) { + const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt index 85aad5fd01..ea99e000ee 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt @@ -23,13 +23,16 @@ import app.revanced.patches.youtube.shared.fingerprints.SeekbarFingerprint import app.revanced.patches.youtube.shared.fingerprints.SeekbarOnDrawFingerprint import app.revanced.patches.youtube.video.information.VideoInformationPatch import app.revanced.patches.youtube.video.videoid.VideoIdPatch -import app.revanced.util.exception +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.StringReference @@ -41,24 +44,11 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference CompatiblePackage( "com.google.android.youtube", [ - "18.48.39", + "18.38.44", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], @@ -92,12 +82,6 @@ object SponsorBlockBytecodePatch : BytecodePatch( "Lapp/revanced/integrations/youtube/sponsorblock/ui/SponsorBlockViewController;" override fun execute(context: BytecodeContext) { - LayoutConstructorFingerprint.result?.let { - if (!ControlsOverlayFingerprint.resolve(context, it.classDef)) { - throw ControlsOverlayFingerprint.exception - } - } ?: throw LayoutConstructorFingerprint.exception - /* * Hook the video time methods */ @@ -108,67 +92,46 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) } - /* - * Set current video id. - */ - VideoIdPatch.hookBackgroundPlayVideoId("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") - - /* - * Seekbar drawing - */ - val seekbarSignatureResult = SeekbarFingerprint.result!!.let { - SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) } - }.result!! - val seekbarMethod = seekbarSignatureResult.mutableMethod - val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions - - /* - * Get left and right of seekbar rectangle - */ - val moveRectangleToRegisterIndex = seekbarMethodInstructions.indexOfFirst { - it.opcode == Opcode.MOVE_OBJECT_FROM16 - } - - seekbarMethod.addInstruction( - moveRectangleToRegisterIndex + 1, - "invoke-static/range {p0 .. p0}, " + - "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;)V", - ) - - for ((index, instruction) in seekbarMethodInstructions.withIndex()) { - if (instruction.opcode != Opcode.INVOKE_STATIC) continue + VideoIdPatch.hookBackgroundPlayVideoId( + "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") - val invokeInstruction = instruction as Instruction35c - if ((invokeInstruction.reference as MethodReference).name != "round") continue - val insertIndex = index + 2 + // Seekbar drawing + SeekbarOnDrawFingerprint.alsoResolve(context, SeekbarFingerprint).mutableMethod.apply { + // Get left and right of seekbar rectangle. + val moveRectangleToRegisterIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT_FROM16) - // set the thickness of the segment - seekbarMethod.addInstruction( - insertIndex, - "invoke-static {v${invokeInstruction.registerC}}, " + - "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V", + addInstruction( + moveRectangleToRegisterIndex + 1, + "invoke-static/range { p0 .. p0 }, " + + "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;)V", ) - break - } - /* - * Draw segment - */ - // Find the drawCircle call and draw the segment before it - for (i in seekbarMethodInstructions.size - 1 downTo 0) { - val invokeInstruction = seekbarMethodInstructions[i] as? ReferenceInstruction ?: continue - if ((invokeInstruction.reference as MethodReference).name != "drawCircle") continue - - val (canvasInstance, centerY) = (invokeInstruction as FiveRegisterInstruction).let { - it.registerC to it.registerE + // Set the thickness of the segment. + val thicknessIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && getReference()?.name == "round" } - seekbarMethod.addInstruction( - i, - "invoke-static {v$canvasInstance, v$centerY}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V", + val thicknessRegister = getInstruction(thicknessIndex).registerC + addInstruction( + thicknessIndex + 2, + "invoke-static { v$thicknessRegister }, " + + "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V", ) - break + // Find the drawCircle call and draw the segment before it. + val drawCircleIndex = indexOfFirstInstructionReversedOrThrow { + getReference()?.name == "drawCircle" + } + val drawCircleInstruction = getInstruction(drawCircleIndex) + val canvasInstanceRegister = drawCircleInstruction.registerC + val centerYRegister = drawCircleInstruction.registerE + + addInstruction( + drawCircleIndex, + "invoke-static { v$canvasInstanceRegister, v$centerYRegister }, " + + "$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->" + + "drawSponsorTimeBars(Landroid/graphics/Canvas;F)V", + ) } // Change visibility of the buttons. @@ -179,24 +142,26 @@ object SponsorBlockBytecodePatch : BytecodePatch( PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR) // Append the new time to the player layout. - val appendTimeFingerprintResult = AppendTimeFingerprint.result!! - val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex - val targetRegister = - (appendTimeFingerprintResult.method.implementation!!.instructions.elementAt(appendTimePatternScanStartIndex + 1) as OneRegisterInstruction).registerA - - appendTimeFingerprintResult.mutableMethod.addInstructions( - appendTimePatternScanStartIndex + 2, - """ - invoke-static {v$targetRegister}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->appendTimeWithoutSegments(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$targetRegister - """, - ) + AppendTimeFingerprint.resultOrThrow().let { + val appendTimePatternScanStartIndex = it.scanResult.patternScanResult!!.startIndex + it.mutableMethod.apply { + val register = getInstruction(appendTimePatternScanStartIndex + 1).registerA + + addInstructions( + appendTimePatternScanStartIndex + 2, + """ + invoke-static { v$register }, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->appendTimeWithoutSegments(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ + ) + } + } - // initialize the player controller + // Initialize the player controller. VideoInformationPatch.onCreateHook(INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR, "initialize") - // initialize the sponsorblock view - ControlsOverlayFingerprint.result?.let { + // Initialize the SponsorBlock view. + ControlsOverlayFingerprint.alsoResolve(context, LayoutConstructorFingerprint).let { val startIndex = it.scanResult.patternScanResult!!.startIndex it.mutableMethod.apply { val frameLayoutRegister = (getInstruction(startIndex + 2) as OneRegisterInstruction).registerA @@ -205,53 +170,50 @@ object SponsorBlockBytecodePatch : BytecodePatch( "invoke-static {v$frameLayoutRegister}, $INTEGRATIONS_SPONSORBLOCK_VIEW_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/ViewGroup;)V", ) } - } ?: throw ControlsOverlayFingerprint.exception - - // get rectangle field name - RectangleFieldInvalidatorFingerprint.resolve(context, seekbarSignatureResult.classDef) - val rectangleFieldInvalidatorInstructions = - RectangleFieldInvalidatorFingerprint.result!!.method.implementation!!.instructions - val rectangleFieldName = - ((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name + } - // replace the "replaceMeWith*" strings - context - .proxy(context.classes.first { it.type.endsWith("SegmentPlaybackController;") }) - .mutableClass - .methods - .find { it.name == "setSponsorBarRect" } - ?.let { method -> - fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) { - val register = (instruction as OneRegisterInstruction).registerA - this.replaceInstruction( - index, - "const-string v$register, \"$with\"", - ) - } - for ((index, it) in method.implementation!!.instructions.withIndex()) { - if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue - when (((it as ReferenceInstruction).reference as StringReference).string) { - "replaceMeWithsetSponsorBarRect" -> method.replaceStringInstruction( + // Set seekbar draw rectangle. + RectangleFieldInvalidatorFingerprint.alsoResolve(context, SeekbarOnDrawFingerprint).mutableMethod.apply { + val fieldIndex = implementation!!.instructions.count() - 3 + val fieldReference = getInstruction(fieldIndex).reference as FieldReference + + // replace the "replaceMeWith*" strings + context + .proxy(context.classes.first { it.type.endsWith("SegmentPlaybackController;") }) + .mutableClass + .methods + .find { it.name == "setSponsorBarRect" } + ?.let { method -> + fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) { + val register = (instruction as OneRegisterInstruction).registerA + this.replaceInstruction( index, - it, - rectangleFieldName, + "const-string v$register, \"$with\"", ) } - } - } ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings") + for ((index, it) in method.implementation!!.instructions.withIndex()) { + if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue + + when (((it as ReferenceInstruction).reference as StringReference).string) { + "replaceMeWithsetSponsorBarRect" -> method.replaceStringInstruction( + index, + it, + fieldReference.name, + ) + } + } + } ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings") + } // The vote and create segment buttons automatically change their visibility when appropriate, // but if buttons are showing when the end of the video is reached then they will not automatically hide. // Add a hook to forcefully hide when the end of the video is reached. - AutoRepeatParentFingerprint.result ?: throw AutoRepeatParentFingerprint.exception - AutoRepeatFingerprint.also { - it.resolve(context, AutoRepeatParentFingerprint.result!!.classDef) - }.result?.mutableMethod?.addInstruction( + AutoRepeatFingerprint.alsoResolve(context, AutoRepeatParentFingerprint).mutableMethod.addInstruction( 0, "invoke-static {}, $INTEGRATIONS_SPONSORBLOCK_VIEW_CONTROLLER_CLASS_DESCRIPTOR->endOfVideoReached()V", - ) ?: throw AutoRepeatFingerprint.exception + ) - // TODO: isSBChannelWhitelisting implementation + // TODO: isSBChannelWhitelisting implementation? } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt index a4aa0f38de..df2fcf9c64 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt @@ -22,30 +22,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt index e4f42397e4..d58e772f18 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt @@ -2,30 +2,47 @@ package app.revanced.patches.youtube.layout.startpage import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.ListPreference -import app.revanced.patches.youtube.layout.startpage.fingerprints.StartActivityFingerprint +import app.revanced.patches.youtube.layout.startpage.fingerprints.BrowseIdFingerprint +import app.revanced.patches.youtube.layout.startpage.fingerprints.IntentActionFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch -import app.revanced.patches.youtube.shared.fingerprints.HomeActivityFingerprint -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.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.StringReference @Patch( name = "Change start page", description = "Adds an option to set which page the app opens in instead of the homepage.", - dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], + dependencies = [ + IntegrationsPatch::class, + SettingsPatch::class, + AddResourcesPatch::class + ], compatiblePackages = [ CompatiblePackage( - "com.google.android.youtube" + "com.google.android.youtube", + [ + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + ] ) ] ) @Suppress("unused") object ChangeStartPagePatch : BytecodePatch( - setOf(HomeActivityFingerprint) + setOf(BrowseIdFingerprint, IntentActionFingerprint) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/ChangeStartPagePatch;" @@ -35,19 +52,31 @@ object ChangeStartPagePatch : BytecodePatch( SettingsPatch.PreferenceScreen.GENERAL_LAYOUT.addPreferences( ListPreference( - key = "revanced_start_page", + key = "revanced_change_start_page", summaryKey = null, ) ) - StartActivityFingerprint.resolve( - context, - HomeActivityFingerprint.result?.classDef ?: throw HomeActivityFingerprint.exception - ) + // Hook browseId. + BrowseIdFingerprint.resultOrThrow().mutableMethod.apply { + val browseIdIndex = indexOfFirstInstructionOrThrow { + getReference()?.string == "FEwhat_to_watch" + } + val browseIdRegister = getInstruction(browseIdIndex).registerA + + addInstructions( + browseIdIndex + 1, """ + invoke-static { v$browseIdRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$browseIdRegister + """ + ) + } - StartActivityFingerprint.result?.mutableMethod?.addInstruction( + // There is no browserId assigned to Shorts and Search. + // Just hook the Intent action. + IntentActionFingerprint.resultOrThrow().mutableMethod.addInstruction( 0, - "invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->changeIntent(Landroid/content/Intent;)V" - ) ?: throw StartActivityFingerprint.exception + "invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideIntentAction(Landroid/content/Intent;)V" + ) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/BrowseIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/BrowseIdFingerprint.kt new file mode 100644 index 0000000000..f3fc189f3e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/BrowseIdFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.layout.startpage.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +internal object BrowseIdFingerprint : MethodFingerprint( + returnType = "Lcom/google/android/apps/youtube/app/common/ui/navigation/PaneDescriptor;", + parameters = listOf(), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.RETURN_OBJECT, + ), + strings = listOf("FEwhat_to_watch") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/StartActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/IntentActionFingerprint.kt similarity index 63% rename from src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/StartActivityFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/IntentActionFingerprint.kt index 7461872181..86c227c29e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/StartActivityFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/fingerprints/IntentActionFingerprint.kt @@ -2,6 +2,7 @@ package app.revanced.patches.youtube.layout.startpage.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint -object StartActivityFingerprint : MethodFingerprint( +internal object IntentActionFingerprint : MethodFingerprint( parameters = listOf("Landroid/content/Intent;"), + strings = listOf("has_handled_intent"), ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt index 3030214399..389c2a1cb9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt @@ -3,20 +3,22 @@ package app.revanced.patches.youtube.layout.startupshortsreset import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsConfigFingerprint +import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsConfigFingerprint.indexOfOptionalInstruction import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch -import app.revanced.util.exception +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference @@ -27,37 +29,18 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] ) @Suppress("unused") object DisableResumingShortsOnStartupPatch : BytecodePatch( - setOf(UserWasInShortsFingerprint) + setOf(UserWasInShortsConfigFingerprint, UserWasInShortsFingerprint) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = @@ -70,30 +53,54 @@ object DisableResumingShortsOnStartupPatch : BytecodePatch( SwitchPreference("revanced_disable_resuming_shorts_player") ) - UserWasInShortsFingerprint.result?.mutableMethod?.apply { + UserWasInShortsConfigFingerprint.resultOrThrow().mutableMethod.apply { + val startIndex = indexOfOptionalInstruction(this) + val walkerIndex = indexOfFirstInstructionOrThrow(startIndex) { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL + && reference?.returnType == "Z" + && reference.definingClass != "Lj${'$'}/util/Optional;" + && reference.parameterTypes.size == 0 + } + + val walkerMethod = context.toMethodWalker(this) + .nextMethod(walkerIndex, true) + .getMethod() as MutableMethod + + // Presumably a method that processes the ProtoDataStore value (boolean) for the 'user_was_in_shorts' key. + walkerMethod.addInstructionsWithLabels( + 0, + """ + invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z + move-result v0 + if-eqz v0, :show + const/4 v0, 0x0 + return v0 + :show + nop + """ + ) + } + + UserWasInShortsFingerprint.resultOrThrow().mutableMethod.apply { val listenableInstructionIndex = indexOfFirstInstructionOrThrow { opcode == Opcode.INVOKE_INTERFACE && getReference()?.definingClass == "Lcom/google/common/util/concurrent/ListenableFuture;" && getReference()?.name == "isDone" } - val originalInstructionRegister = getInstruction(listenableInstructionIndex).registerC val freeRegister = getInstruction(listenableInstructionIndex + 1).registerA - // Replace original instruction to preserve control flow label. - replaceInstruction( + addInstructionsAtControlFlowLabel( listenableInstructionIndex, - "invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z" - ) - addInstructionsWithLabels( - listenableInstructionIndex + 1, """ + invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z move-result v$freeRegister if-eqz v$freeRegister, :show_startup_shorts_player return-void :show_startup_shorts_player - invoke-interface {v$originalInstructionRegister}, Lcom/google/common/util/concurrent/ListenableFuture;->isDone()Z + nop """ ) - } ?: throw UserWasInShortsFingerprint.exception + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsConfigFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsConfigFingerprint.kt new file mode 100644 index 0000000000..21bf07c246 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsConfigFingerprint.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.youtube.layout.startupshortsreset.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsConfigFingerprint.indexOfOptionalInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference +import com.android.tools.smali.dexlib2.util.MethodUtil + +/** + * 18.15.40+ + */ +internal object UserWasInShortsConfigFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("Failed to get offline response: "), + customFingerprint = { methodDef, _ -> + indexOfOptionalInstruction(methodDef) >= 0 + } +) { + private val optionalOfMethodReference = ImmutableMethodReference( + "Lj${'$'}/util/Optional;", + "of", + listOf("Ljava/lang/Object;"), + "Lj${'$'}/util/Optional;", + ) + + fun indexOfOptionalInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + val reference = getReference() ?: return@indexOfFirstInstruction false + + MethodUtil.methodSignaturesMatch(reference, optionalOfMethodReference) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt index 8b41fc5dfc..d5c3aed445 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt @@ -26,30 +26,11 @@ import app.revanced.util.resultOrThrow compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", arrayOf( - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", - "19.16.39" + "19.16.39", + "19.25.37", + "19.34.42", ) ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt index 23d7a62fdd..eb476cf337 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt @@ -35,29 +35,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction CompatiblePackage( "com.google.android.youtube", [ - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeResourcePatch.kt index 80cff8a722..98606ab8c5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeResourcePatch.kt @@ -11,6 +11,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.youtube.layout.theme.ThemeBytecodePatch.darkThemeBackgroundColor import app.revanced.patches.youtube.layout.theme.ThemeBytecodePatch.lightThemeBackgroundColor +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import org.w3c.dom.Element @@ -19,6 +20,7 @@ import org.w3c.dom.Element SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class, + VersionCheckPatch::class, ], ) internal object ThemeResourcePatch : ResourcePatch() { @@ -89,12 +91,35 @@ internal object ThemeResourcePatch : ResourcePatch() { return@editSplashScreen } } + throw PatchException("Failed to modify launch screen") } } + + // Fix the splash screen dark mode background color. + // In earlier versions of the app this is white and makes no sense for dark mode. + // This is only required for 19.32 and greater, but is applied to all targets. + // Only dark mode needs this fix as light mode correctly uses the custom color. + context.xmlEditor["res/values-night/styles.xml"].use { editor -> + val document = editor.file + + // Create a night mode specific override for the splash screen background. + val style = document.createElement("style") + style.setAttribute("name", "Theme.YouTube.Home") + style.setAttribute("parent", "@style/Base.V23.Theme.YouTube.Home") + + val windowItem = document.createElement("item") + windowItem.setAttribute("name", "android:windowBackground") + windowItem.textContent = "@color/$SPLASH_BACKGROUND_COLOR" + style.appendChild(windowItem) + + val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + resourcesNode.appendChild(style) + } } } + @Suppress("SameParameterValue") private fun addColorResource( context: ResourceContext, resourceFile: String, diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt index 095d567c72..f4088bbcae 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt @@ -28,30 +28,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictions.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictions.kt index d1d9dd39e1..8ca354b88f 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictions.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictions.kt @@ -24,30 +24,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt index 1db64983dc..a9521378fd 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt @@ -23,30 +23,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index 97fc73d662..c639abb5e8 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -3,6 +3,7 @@ package app.revanced.patches.youtube.misc.backgroundplayback import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch @@ -14,7 +15,11 @@ import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.video.information.VideoInformationPatch +import app.revanced.util.addInstructionsAtControlFlowLabel +import app.revanced.util.findOpcodeIndicesReversed import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference @@ -32,24 +37,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference CompatiblePackage( "com.google.android.youtube", [ - "18.48.39", + "18.38.44", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] @@ -66,14 +58,19 @@ object BackgroundPlaybackPatch : BytecodePatch( "Lapp/revanced/integrations/youtube/patches/BackgroundPlaybackPatch;" override fun execute(context: BytecodeContext) { - BackgroundPlaybackManagerFingerprint.resultOrThrow().mutableMethod.addInstructions( - 0, - """ - invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->playbackIsNotShort()Z - move-result v0 - return v0 - """ - ) + BackgroundPlaybackManagerFingerprint.resultOrThrow().mutableMethod.apply { + findOpcodeIndicesReversed(Opcode.RETURN).forEach{ index -> + val register = getInstruction(index).registerA + + addInstructionsAtControlFlowLabel( + index, + """ + invoke-static { v$register }, $INTEGRATIONS_CLASS_DESCRIPTOR->allowBackgroundPlayback(Z)Z + move-result v$register + """ + ) + } + } // Enable background playback option in YouTube settings BackgroundPlaybackSettingsFingerprint.resultOrThrow().mutableMethod.apply { diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerFingerprint.kt index 7f33326c5f..11040d5e18 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerFingerprint.kt @@ -17,13 +17,7 @@ internal object KidsBackgroundPlaybackPolicyControllerFingerprint : LiteralValue Opcode.IGET, Opcode.CONST_4, Opcode.IF_NE, - Opcode.IGET_OBJECT, - Opcode.SGET_OBJECT, - Opcode.IF_EQ, - Opcode.GOTO, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.RETURN_VOID + Opcode.IGET_OBJECT ), literalSupplier = { 5 }, ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt index 70e1978e76..3c3e2c45ab 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt @@ -21,27 +21,10 @@ import app.revanced.util.exception "com.google.android.youtube", [ "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ] ) ] diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt index 7d24b10862..d816f24a68 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt @@ -18,30 +18,11 @@ import app.revanced.util.resultOrThrow CompatiblePackage( "com.google.android.youtube", [ - "18.32.39", - "18.37.36", "18.38.44", - "18.43.45", - "18.44.41", - "18.45.43", - "18.48.39", "18.49.37", - "19.01.34", - "19.02.39", - "19.03.36", - "19.04.38", - "19.05.36", - "19.06.39", - "19.07.40", - "19.08.36", - "19.09.38", - "19.10.39", - "19.11.43", - "19.12.41", - "19.13.37", - "19.14.43", - "19.15.36", "19.16.39", + "19.25.37", + "19.34.42", ], ), ], diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/DisableCairoSettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/DisableCairoSettingsPatch.kt new file mode 100644 index 0000000000..b936d7bf91 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/DisableCairoSettingsPatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.youtube.misc.fix.cairo + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch +import app.revanced.patches.youtube.misc.fix.cairo.fingerprints.CarioFragmentConfigFingerprint +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch( + description = "Disables Cairo Fragment from being used.", + dependencies = [ + VersionCheckPatch::class + ] +) +internal object DisableCairoSettingsPatch : BytecodePatch( + setOf(CarioFragmentConfigFingerprint) +) { + override fun execute(context: BytecodeContext) { + if (!VersionCheckPatch.is_19_04_or_greater) { + return + } + + /** + *
+         * Cairo Fragment was added since YouTube v19.04.38.
+         *
+         * Disable this for the following reasons:
+         * 1. [BackgroundPlaybackPatch] does not activate the Minimized playback setting of Cairo Fragment.
+         * 2. Some patches do not yet support Cairo Fragments (ie: custom Seekbar color).
+         * 3. Settings preferences added by ReVanced are missing.
+         *
+         * Screenshots of the Cairo Fragment:
+         * uYouPlus#1468.
+         */
+        CarioFragmentConfigFingerprint.resultOrThrow().mutableMethod.apply {
+            val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(
+                CarioFragmentConfigFingerprint.CAIRO_CONFIG_LITERAL_VALUE
+            )
+            val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
+            val register = getInstruction(resultIndex).registerA
+
+            addInstruction(
+                resultIndex + 1,
+                "const/16 v$register, 0x0"
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/fingerprints/CarioFragmentConfigFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/fingerprints/CarioFragmentConfigFingerprint.kt
new file mode 100644
index 0000000000..fc24be05c2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/fingerprints/CarioFragmentConfigFingerprint.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.youtube.misc.fix.cairo.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patches.youtube.misc.fix.cairo.fingerprints.CarioFragmentConfigFingerprint.CAIRO_CONFIG_LITERAL_VALUE
+import app.revanced.util.patch.LiteralValueFingerprint
+import com.android.tools.smali.dexlib2.AccessFlags
+
+/**
+ * Added in YouTube v19.04.38
+ *
+ * When this value is TRUE, Cairo Fragment is used.
+ * In this case, some of patches may be broken, so set this value to FALSE.
+ */
+internal object CarioFragmentConfigFingerprint : LiteralValueFingerprint(
+    returnType = "Z",
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+    literalSupplier = { CAIRO_CONFIG_LITERAL_VALUE }
+) {
+  const val CAIRO_CONFIG_LITERAL_VALUE = 45532100L
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch.kt
index ffb7aaf675..f97553fc87 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch.kt
@@ -49,29 +49,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.37.36",
                 "18.38.44",
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ],
         ),
     ],
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt
index e74a3d11eb..e66efc9c60 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt
@@ -3,7 +3,7 @@ package app.revanced.patches.youtube.misc.gms
 import app.revanced.patches.shared.fingerprints.CastContextFetchFingerprint
 import app.revanced.patches.shared.misc.gms.BaseGmsCoreSupportPatch
 import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch
-import app.revanced.patches.youtube.misc.fix.playback.SpoofClientPatch
+import app.revanced.patches.youtube.misc.fix.playback.SpoofVideoStreamsPatch
 import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME
 import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
 import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorGroupIdOption
@@ -25,36 +25,18 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
     integrationsPatchDependency = IntegrationsPatch::class,
     dependencies = setOf(
         HideCastButtonPatch::class,
-        SpoofClientPatch::class,
+        SpoofVideoStreamsPatch::class,
     ),
     gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
     compatiblePackages = setOf(
         CompatiblePackage(
             "com.google.android.youtube",
             setOf(
-                "18.37.36",
                 "18.38.44",
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ),
         ),
     ),
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 d85b5f6a1b..84e1c061c2 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
@@ -97,9 +97,8 @@ object CronetImageUrlHook : BytecodePatch(
         // 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.
         RequestFingerprint.resultOrThrow().apply {
-            // The url is the only string field that is set inside the constructor.
-            val urlFieldInstruction = mutableMethod.getInstructions().single {
-                if (it.opcode != Opcode.IPUT_OBJECT) return@single false
+            val urlFieldInstruction = mutableMethod.getInstructions().first {
+                if (it.opcode != Opcode.IPUT_OBJECT) return@first false
 
                 val reference = (it as ReferenceInstruction).reference as FieldReference
                 reference.type == "Ljava/lang/String;"
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/IntegrationsPatch.kt
index ed161b2540..1c426f6cd1 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/IntegrationsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/IntegrationsPatch.kt
@@ -2,17 +2,11 @@ package app.revanced.patches.youtube.misc.integrations
 
 import app.revanced.patcher.patch.annotation.Patch
 import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
-import app.revanced.patches.youtube.misc.integrations.fingerprints.*
+import app.revanced.patches.youtube.misc.integrations.fingerprints.ApplicationInitFingerprint
 
 @Patch(requiresIntegrations = true)
 object IntegrationsPatch : BaseIntegrationsPatch(
     setOf(
         ApplicationInitFingerprint,
-        StandalonePlayerActivityFingerprint,
-        RemoteEmbeddedPlayerFingerprint,
-        RemoteEmbedFragmentFingerprint,
-        EmbeddedPlayerControlsOverlayFingerprint,
-        EmbeddedPlayerFingerprint,
-        APIPlayerServiceFingerprint,
     ),
 )
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/APIPlayerServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/APIPlayerServiceFingerprint.kt
deleted file mode 100644
index ca4ad846e6..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/APIPlayerServiceFingerprint.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * For embedded playback.
- * It appears this hook may no longer be needed as one of the constructor parameters is the already hooked
- * [EmbeddedPlayerControlsOverlayFingerprint]
- */
-internal object APIPlayerServiceFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
-    customFingerprint = { methodDef, _ -> methodDef.definingClass == "Lcom/google/android/apps/youtube/embeddedplayer/service/service/jar/ApiPlayerService;" },
-    // Integrations context is the first method parameter.
-    contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size }
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerControlsOverlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerControlsOverlayFingerprint.kt
deleted file mode 100644
index 1b57540675..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerControlsOverlayFingerprint.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * For embedded playback inside Google Play store (and probably other situations as well).
- *
- * Note: this fingerprint may no longer be needed, as it appears
- * [RemoteEmbedFragmentFingerprint] may be set before this hook is called.
- */
-internal object EmbeddedPlayerControlsOverlayFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PRIVATE or AccessFlags.CONSTRUCTOR,
-    returnType = "V",
-    parameters = listOf("Landroid/content/Context;", "L", "L"),
-    customFingerprint = { methodDef, _ ->
-        methodDef.definingClass.startsWith("Lcom/google/android/apps/youtube/embeddedplayer/service/ui/overlays/controlsoverlay/remoteloaded/")
-    },
-    // Integrations context is the first method parameter.
-    contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size }
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerFingerprint.kt
deleted file mode 100644
index fbc2ab0dbf..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/EmbeddedPlayerFingerprint.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * For embedded playback inside the Google app (such as the in app 'discover' tab).
- *
- * Note: this fingerprint may or may not be needed, as
- * [RemoteEmbedFragmentFingerprint] might be set before this is called.
- */
-internal object EmbeddedPlayerFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
-    returnType = "L",
-    parameters = listOf("L", "L", "Landroid/content/Context;"),
-    strings = listOf("android.hardware.type.television"), // String is also found in other classes
-    // Integrations context is the third method parameter.
-    contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size + 2 }
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbedFragmentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbedFragmentFingerprint.kt
deleted file mode 100644
index 77eeec32dc..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbedFragmentFingerprint.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * For embedded playback.  Likely covers Google Play store and other Google products.
- */
-internal object RemoteEmbedFragmentFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
-    returnType = "V",
-    parameters = listOf("Landroid/content/Context;", "L", "L"),
-    customFingerprint = { methodDef, _ ->
-        methodDef.definingClass == "Lcom/google/android/apps/youtube/embeddedplayer/service/jar/client/RemoteEmbedFragment;"
-    },
-    // Integrations context is the first method parameter.
-    contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size }
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbeddedPlayerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbeddedPlayerFingerprint.kt
deleted file mode 100644
index 196994f49b..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/RemoteEmbeddedPlayerFingerprint.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * For embedded playback inside 3rd party android app (such as 3rd party Reddit apps).
- */
-internal object RemoteEmbeddedPlayerFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PRIVATE or AccessFlags.CONSTRUCTOR,
-    returnType = "V",
-    parameters = listOf("Landroid/content/Context;", "L", "L", "Z"),
-    customFingerprint = { methodDef, _ ->
-        methodDef.definingClass == "Lcom/google/android/youtube/api/jar/client/RemoteEmbeddedPlayer;"
-    },
-    // Integrations context is the first method parameter.
-    contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size }
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt
deleted file mode 100644
index 9148f5fdb5..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package app.revanced.patches.youtube.misc.integrations.fingerprints
-
-import app.revanced.patcher.extensions.or
-import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
-import com.android.tools.smali.dexlib2.AccessFlags
-
-/**
- * Old API activity to embed YouTube into 3rd party Android apps.
- *
- * In 2023 supported was ended and is no longer available,
- * but this may still be used by older apps:
- * https://developers.google.com/youtube/android/player
- */
-internal object StandalonePlayerActivityFingerprint : IntegrationsFingerprint(
-    accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
-    returnType = "V",
-    parameters = listOf("L"),
-    customFingerprint = { methodDef, _ ->
-        methodDef.definingClass == "Lcom/google/android/youtube/api/StandalonePlayerActivity;"
-                && methodDef.name == "onCreate"
-    },
-    // Integrations context is the Activity itself.
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt
index 451f7742e7..71052a3f3c 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt
@@ -10,47 +10,48 @@ import app.revanced.patches.all.misc.resources.AddResourcesPatch
 import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
 import app.revanced.patches.youtube.misc.links.fingerprints.ABUriParserFingerprint
+import app.revanced.patches.youtube.misc.links.fingerprints.ABUriParserLegacyFingerprint
 import app.revanced.patches.youtube.misc.links.fingerprints.HTTPUriParserFingerprint
+import app.revanced.patches.youtube.misc.links.fingerprints.HTTPUriParserLegacyFingerprint
+import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch
 import app.revanced.patches.youtube.misc.settings.SettingsPatch
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstruction
 import app.revanced.util.resultOrThrow
+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(
     name = "Bypass URL redirects",
     description = "Adds an option to bypass URL redirects and open the original URL directly.",
-    dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class],
+    dependencies = [
+        IntegrationsPatch::class,
+        SettingsPatch::class,
+        AddResourcesPatch::class,
+        VersionCheckPatch::class
+   ],
     compatiblePackages = [
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
+                "18.38.44",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ],
         ),
     ],
 )
 @Suppress("unused")
 object BypassURLRedirectsPatch : BytecodePatch(
-    setOf(ABUriParserFingerprint, HTTPUriParserFingerprint),
+    setOf(
+        ABUriParserFingerprint,
+        ABUriParserLegacyFingerprint,
+        HTTPUriParserFingerprint,
+        HTTPUriParserLegacyFingerprint,
+    ),
 ) {
     override fun execute(context: BytecodeContext) {
         AddResourcesPatch(this::class)
@@ -59,24 +60,39 @@ object BypassURLRedirectsPatch : BytecodePatch(
             SwitchPreference("revanced_bypass_url_redirects"),
         )
 
-        mapOf(
-            ABUriParserFingerprint to 7, // Offset to Uri.parse.
-            HTTPUriParserFingerprint to 0, // Offset to Uri.parse.
-        ).map { (fingerprint, offset) ->
-            fingerprint.resultOrThrow() to offset
-        }.forEach { (result, offset) ->
-            result.mutableMethod.apply {
-                val insertIndex = result.scanResult.patternScanResult!!.startIndex + offset
-                val uriStringRegister = getInstruction(insertIndex).registerC
-
-                replaceInstruction(
-                    insertIndex,
-                    "invoke-static {v$uriStringRegister}," +
-                        "Lapp/revanced/integrations/youtube/patches/BypassURLRedirectsPatch;" +
-                        "->" +
-                        "parseRedirectUri(Ljava/lang/String;)Landroid/net/Uri;",
+        val fingerprints =
+            if (VersionCheckPatch.is_19_33_or_greater)
+                arrayOf(
+                    ABUriParserFingerprint,
+                    HTTPUriParserFingerprint
                 )
+            else arrayOf(
+                ABUriParserLegacyFingerprint,
+                HTTPUriParserLegacyFingerprint
+            )
+
+        fingerprints.forEach { fingerprint ->
+            fingerprint.resultOrThrow().let {
+                it.mutableMethod.apply {
+                    val insertIndex = findUriParseIndex()
+
+                    val uriStringRegister = getInstruction(insertIndex).registerC
+
+                    replaceInstruction(
+                        insertIndex,
+                        "invoke-static {v$uriStringRegister}," +
+                                "Lapp/revanced/integrations/youtube/patches/BypassURLRedirectsPatch;" +
+                                "->" +
+                                "parseRedirectUri(Ljava/lang/String;)Landroid/net/Uri;",
+                    )
+                }
             }
         }
     }
+
+    internal fun Method.findUriParseIndex(): Int = indexOfFirstInstruction {
+        val reference = getReference()
+        reference?.returnType == "Landroid/net/Uri;" &&
+                reference.name == "parse"
+    }
 }
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt
index 97367d6c1b..a19ff03cec 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt
@@ -24,30 +24,11 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.32.39",
-                "18.37.36",
                 "18.38.44",
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ]
         )
     ]
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserFingerprint.kt
index c2b3aa1d66..f5766ae30f 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserFingerprint.kt
@@ -2,32 +2,21 @@ package app.revanced.patches.youtube.misc.links.fingerprints
 
 import app.revanced.patcher.extensions.or
 import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.misc.links.BypassURLRedirectsPatch.findUriParseIndex
 import com.android.tools.smali.dexlib2.AccessFlags
-import com.android.tools.smali.dexlib2.Opcode
 
+/**
+ * Target 19.33+
+ */
 internal object ABUriParserFingerprint : MethodFingerprint(
     returnType = "Ljava/lang/Object",
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     parameters = listOf("Ljava/lang/Object"),
-    opcodes = listOf(
-        Opcode.RETURN_OBJECT,
-        Opcode.CHECK_CAST,
-        Opcode.INVOKE_VIRTUAL,
-        Opcode.MOVE_RESULT_OBJECT,
-        Opcode.CHECK_CAST,
-        Opcode.RETURN_OBJECT,
-        Opcode.CHECK_CAST,
-        Opcode.INVOKE_STATIC,
-        Opcode.MOVE_RESULT_OBJECT,
-        Opcode.RETURN_OBJECT,
-        Opcode.CHECK_CAST,
+    strings = listOf(
+        "Found entityKey=`",
+        "` that does not contain a PlaylistVideoEntityId message as it's identifier."
     ),
-    customFingerprint = custom@{ methodDef, classDef ->
-        // This method is always called "a" because this kind of class always has a single (non synthetic) method.
-
-        if (methodDef.name != "a") return@custom false
-
-        val count = classDef.methods.count()
-        count == 2 || count == 3
-    },
+    customFingerprint = { methodDef, _ ->
+        methodDef.findUriParseIndex() >= 0
+    }
 )
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserLegacyFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserLegacyFingerprint.kt
new file mode 100644
index 0000000000..b05818e4b7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/ABUriParserLegacyFingerprint.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.youtube.misc.links.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 ABUriParserLegacyFingerprint : MethodFingerprint(
+    returnType = "Ljava/lang/Object",
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+    parameters = listOf("Ljava/lang/Object"),
+    opcodes = listOf(
+        Opcode.RETURN_OBJECT,
+        Opcode.CHECK_CAST,
+        Opcode.INVOKE_VIRTUAL,
+        Opcode.MOVE_RESULT_OBJECT,
+        Opcode.CHECK_CAST,
+        Opcode.RETURN_OBJECT,
+        Opcode.CHECK_CAST,
+        Opcode.INVOKE_STATIC,
+        Opcode.MOVE_RESULT_OBJECT,
+        Opcode.RETURN_OBJECT,
+        Opcode.CHECK_CAST,
+    ),
+    customFingerprint = custom@{ methodDef, classDef ->
+        // This method is always called "a" because this kind of class always has a single (non synthetic) method.
+
+        if (methodDef.name != "a") return@custom false
+
+        val count = classDef.methods.count()
+        count == 2 || count == 3
+    }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserFingerprint.kt
index 66fcccaac4..f27f23949b 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserFingerprint.kt
@@ -2,16 +2,18 @@ package app.revanced.patches.youtube.misc.links.fingerprints
 
 import app.revanced.patcher.extensions.or
 import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.misc.links.BypassURLRedirectsPatch.findUriParseIndex
 import com.android.tools.smali.dexlib2.AccessFlags
-import com.android.tools.smali.dexlib2.Opcode
 
+/**
+ * Target 19.33+
+ */
 internal object HTTPUriParserFingerprint : MethodFingerprint(
     returnType = "Landroid/net/Uri",
     accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
     parameters = listOf("Ljava/lang/String"),
-    opcodes = listOf(
-        Opcode.INVOKE_STATIC,
-        Opcode.MOVE_RESULT_OBJECT
-    ),
-    strings = listOf("://")
+    strings = listOf("https", "https:", "://"),
+    customFingerprint = { methodDef, _ ->
+        methodDef.findUriParseIndex() >= 0
+    }
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserLegacyFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserLegacyFingerprint.kt
new file mode 100644
index 0000000000..e6bfc4cf8e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/links/fingerprints/HTTPUriParserLegacyFingerprint.kt
@@ -0,0 +1,17 @@
+package app.revanced.patches.youtube.misc.links.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 HTTPUriParserLegacyFingerprint : MethodFingerprint(
+    returnType = "Landroid/net/Uri",
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
+    parameters = listOf("Ljava/lang/String"),
+    opcodes = listOf(
+        Opcode.INVOKE_STATIC,
+        Opcode.MOVE_RESULT_OBJECT
+    ),
+    strings = listOf("://")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt
index 4b9620a2a4..08c73e9b82 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt
@@ -1,6 +1,5 @@
 package app.revanced.patches.youtube.misc.litho.filter
 
-import app.revanced.util.exception
 import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
@@ -8,36 +7,47 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
 import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
 import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
-import app.revanced.patcher.fingerprint.MethodFingerprint
 import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchException
 import app.revanced.patcher.patch.annotation.Patch
 import app.revanced.patcher.util.smali.ExternalLabel
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
-import app.revanced.patches.youtube.misc.litho.filter.fingerprints.*
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.ComponentContextParserFingerprint
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.EmptyComponentFingerprint
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.LithoFilterFingerprint
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.ProtobufBufferReferenceFingerprint
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.ReadComponentIdentifierFingerprint
+import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch
 import app.revanced.util.getReference
 import app.revanced.util.indexOfFirstInstructionOrThrow
+import app.revanced.util.indexOfFirstInstructionReversedOrThrow
+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.iface.instruction.FiveRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.Field
+import com.android.tools.smali.dexlib2.iface.Method
 import com.android.tools.smali.dexlib2.iface.instruction.Instruction
 import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
 import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
+import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
 import com.android.tools.smali.dexlib2.iface.reference.FieldReference
+import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 import java.io.Closeable
 
 @Patch(
     description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
-    dependencies = [IntegrationsPatch::class]
+    dependencies = [IntegrationsPatch::class, VersionCheckPatch::class]
 )
 @Suppress("unused")
 object LithoFilterPatch : BytecodePatch(
-    setOf(ComponentContextParserFingerprint, LithoFilterFingerprint, ProtobufBufferReferenceFingerprint)
+    setOf(
+        ComponentContextParserFingerprint,
+        LithoFilterFingerprint,
+        ProtobufBufferReferenceFingerprint,
+        ReadComponentIdentifierFingerprint,
+        EmptyComponentFingerprint
+    )
 ), Closeable {
-    private val MethodFingerprint.patternScanResult
-        get() = result!!.scanResult.patternScanResult!!
-
-    private val MethodFingerprint.patternScanEndIndex
-        get() = patternScanResult.endIndex
-
     private val Instruction.descriptor
         get() = (this as ReferenceInstruction).reference.toString()
 
@@ -70,118 +80,181 @@ object LithoFilterPatch : BytecodePatch(
      *   }
      * }
      *
+     * When patching 19.17 and earlier:
+     *
      * class ComponentContextParser {
+     *    public ComponentContext ReadComponentIdentifierFingerprint(...) {
+     *        ...
+     *        if (IntegrationsClass.filter(identifier, pathBuilder)); // Inserted by this patch.
+     *            return emptyComponent;
+     *        ...
+     *    }
+     * }
+     *
+     * When patching 19.18 and later:
      *
+     * class ComponentContextParser {
      *    public ComponentContext parseBytesToComponentContext(...) {
      *        ...
-     *        if (IntegrationsClass.filter(identifier, pathBuilder)); // Inserted by this patch.
+     *        if (ReadComponentIdentifierFingerprint() == null); // Inserted by this patch.
      *            return emptyComponent;
      *        ...
      *    }
+     *
+     *    public ComponentIdentifierObj readComponentIdentifier(...) {
+     *        ...
+     *        if (IntegrationsClass.filter(identifier, pathBuilder)); // Inserted by this patch.
+     *            return null;
+     *        ...
+     *    }
      * }
      */
     override fun execute(context: BytecodeContext) {
-        ComponentContextParserFingerprint.result?.also {
-            arrayOf(
-                EmptyComponentBuilderFingerprint,
-                ReadComponentIdentifierFingerprint
-            ).forEach { fingerprint ->
-                if (fingerprint.resolve(context, it.mutableMethod, it.mutableClass)) return@forEach
-                throw fingerprint.exception
+
+        // Remove dummy filter from Integrations static field
+        // and add the filters included during patching.
+        LithoFilterFingerprint.resultOrThrow().mutableMethod.apply {
+            removeInstructions(2, 4) // Remove dummy filter.
+
+            addFilter = { classDescriptor ->
+                addInstructions(
+                    2,
+                    """
+                        new-instance v1, $classDescriptor
+                        invoke-direct {v1}, $classDescriptor->()V
+                        const/16 v2, ${filterCount++}
+                        aput-object v1, v0, v2
+                    """
+                )
             }
-        }?.let { bytesToComponentContextMethod ->
+        }
 
-            // region Pass the buffer into Integrations.
+        // region Pass the buffer into Integrations.
 
-            ProtobufBufferReferenceFingerprint.result
-                ?.mutableMethod?.addInstruction(0,
-                    " invoke-static { p2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V")
-                ?: throw ProtobufBufferReferenceFingerprint.exception
+        ProtobufBufferReferenceFingerprint.resultOrThrow().mutableMethod.addInstruction(
+            0,
+            " invoke-static { p2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V"
+        )
 
-            // endregion
+        // endregion
 
-            // region Hook the method that parses bytes into a ComponentContext.
+        // region Hook the method that parses bytes into a ComponentContext.
 
-            val builderMethodIndex = EmptyComponentBuilderFingerprint.patternScanEndIndex
-            val emptyComponentFieldIndex = builderMethodIndex + 2
+        var readComponentMethod : Method
+        var builderMethodDescriptor : Method
+        val emptyComponentField : Field
 
-            bytesToComponentContextMethod.mutableMethod.apply {
-                val insertHookIndex = indexOfFirstInstructionOrThrow {
-                    opcode == Opcode.IPUT_OBJECT &&
-                            getReference()?.type == "Ljava/lang/StringBuilder;"
-                } + 1
+        ComponentContextParserFingerprint.resultOrThrow().let {
+            it.mutableMethod.apply {
+                // Get the only static method in the class.
+                builderMethodDescriptor = EmptyComponentFingerprint.resultOrThrow().classDef
+                    .methods.first { method -> AccessFlags.STATIC.isSet(method.accessFlags) }
+                // Only one field.
+                emptyComponentField = context.findClass(builderMethodDescriptor.returnType)!!
+                    .immutableClass.fields.single()
+                readComponentMethod = ReadComponentIdentifierFingerprint.resultOrThrow().method
 
-                // region Get free registers that this patch uses.
-                // Registers are overwritten right after they are used in this patch, therefore free to clobber.
+                // 19.18 and later require patching 2 methods instead of one.
+                // Otherwise the patched code is the same.
+                if (VersionCheckPatch.is_19_18_or_greater) {
+                    // Get the method name of the ReadComponentIdentifierFingerprint call.
+                    val readComponentMethodCallIndex = indexOfFirstInstructionOrThrow {
+                        val reference = getReference()
+                        reference?.definingClass == readComponentMethod.definingClass
+                                && reference.name == readComponentMethod.name
+                    }
 
-                val freeRegistersInstruction = getInstruction(insertHookIndex - 2)
+                    // Result of read component, and also a free register.
+                    val register = getInstruction(
+                        readComponentMethodCallIndex + 1
+                    ).registerA
 
-                // Later used to store the protobuf buffer object.
-                val free1 = getInstruction(insertHookIndex).registerA
-                // Later used to store the identifier of the component.
-                // This register currently holds a reference to the StringBuilder object
-                // that is required before clobbering.
-                val free2 = freeRegistersInstruction.registerC
+                    // Insert after 'move-result-object'
+                    val insertHookIndex = readComponentMethodCallIndex + 2
 
-                @Suppress("UnnecessaryVariable")
-                val stringBuilderRegister = free2
+                    // Return an EmptyComponent instead of the original component if the filterState method returns true.
+                    addInstructionsWithLabels(
+                        insertHookIndex,
+                        """
+                            if-nez v$register, :unfiltered
+    
+                            # Component was filtered in ReadComponentIdentifierFingerprint hook
+                            move-object/from16 v$register, p1
+                            invoke-static { v$register }, $builderMethodDescriptor
+                            move-result-object v$register
+                            iget-object v$register, v$register, $emptyComponentField
+                            return-object v$register
+                        """,
+                        ExternalLabel("unfiltered", getInstruction(insertHookIndex))
+                    )
+                }
+            }
+        }
+
+        // endregion
 
-                // endregion
+        // region Read component then store the result.
 
-                // region Get references that this patch needs.
+        ReadComponentIdentifierFingerprint.resultOrThrow().let {
+            it.mutableMethod.apply {
+                val insertHookIndex = indexOfFirstInstructionOrThrow {
+                    opcode == Opcode.IPUT_OBJECT &&
+                            getReference()?.type == "Ljava/lang/StringBuilder;"
+                }
+                val stringBuilderRegister = getInstruction(insertHookIndex).registerA
 
-                val builderMethodDescriptor = getInstruction(builderMethodIndex).descriptor
-                val emptyComponentFieldDescriptor = getInstruction(emptyComponentFieldIndex).descriptor
+                // Identifier is saved to a field just before the string builder.
+                val identifierRegister = getInstruction(
+                    indexOfFirstInstructionReversedOrThrow(insertHookIndex) {
+                        opcode == Opcode.IPUT_OBJECT
+                                && getReference()?.type == "Ljava/lang/String;"
+                    }
+                ).registerA
 
-                val identifierRegister =
-                    getInstruction(ReadComponentIdentifierFingerprint.patternScanEndIndex).registerA
+                // Find a free temporary register.
+                val register = getInstruction(
+                    // Immediately before is a StringBuilder append constant character.
+                    indexOfFirstInstructionReversedOrThrow(insertHookIndex, Opcode.CONST_16)
+                ).registerA
 
-                // endregion
+                // Verify the temp register will not clobber the method result register.
+                if (stringBuilderRegister == register) {
+                    throw PatchException("Free register will clobber StringBuilder register")
+                }
 
-                // region Patch the method.
+                val commonInstructions = """
+                    invoke-static { v$identifierRegister, v$stringBuilderRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
+                    move-result v$register
+                    if-eqz v$register, :unfiltered
+                """
 
-                // Insert the instructions that are responsible
-                // to return an EmptyComponent instead of the original component if the filter method returns true.
                 addInstructionsWithLabels(
                     insertHookIndex,
+                    if (VersionCheckPatch.is_19_18_or_greater) """
+                        $commonInstructions
+                        
+                        # Return null, and the ComponentContextParserFingerprint hook 
+                        # handles returning an empty component.
+                        const/4 v$register, 0x0
+                        return-object v$register
                     """
-                        # Invoke the filter method.
-                      
-                        invoke-static { v$identifierRegister, v$stringBuilderRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
-                        move-result v$free1
-                       
-                        if-eqz v$free1, :unfiltered
-
-                        move-object/from16 v$free2, p1
-                        invoke-static {v$free2}, $builderMethodDescriptor
-                        move-result-object v$free2
-                        iget-object v$free2, v$free2, $emptyComponentFieldDescriptor
-                        return-object v$free2
+                    else """
+                        $commonInstructions
+                        
+                        # Exact same code as ComponentContextParserFingerprint hook,
+                        # but with the free register of this method.
+                        move-object/from16 v$register, p1
+                        invoke-static { v$register }, $builderMethodDescriptor
+                        move-result-object v$register
+                        iget-object v$register, v$register, $emptyComponentField
+                        return-object v$register
                     """,
-                    // Used to jump over the instruction which block the component from being created.
                     ExternalLabel("unfiltered", getInstruction(insertHookIndex))
                 )
-                // endregion
             }
+        }
 
-            // endregion
-        } ?: throw ComponentContextParserFingerprint.exception
-
-        LithoFilterFingerprint.result?.mutableMethod?.apply {
-            removeInstructions(2, 4) // Remove dummy filter.
-
-            addFilter = { classDescriptor ->
-                addInstructions(
-                    2,
-                    """
-                        new-instance v1, $classDescriptor
-                        invoke-direct {v1}, $classDescriptor->()V
-                        const/16 v2, ${filterCount++}
-                        aput-object v1, v0, v2
-                    """
-                )
-            }
-        } ?: throw LithoFilterFingerprint.exception
+        // endregion
     }
 
     override fun close() = LithoFilterFingerprint.result!!
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt
index 419e0f91c8..1249e674c8 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt
@@ -2,6 +2,10 @@ package app.revanced.patches.youtube.misc.litho.filter.fingerprints
 
 import app.revanced.patcher.fingerprint.MethodFingerprint
 
+/**
+ * In 19.17 and earlier, this resolves to the same method as [ReadComponentIdentifierFingerprint].
+ * In 19.18+ this resolves to a different method.
+ */
 internal object ComponentContextParserFingerprint : MethodFingerprint(
     strings = listOf("Component was not found %s because it was removed due to duplicate converter bindings.")
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt
deleted file mode 100644
index ec6a1f0c6f..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package app.revanced.patches.youtube.misc.litho.filter.fingerprints
-
-import app.revanced.patcher.fingerprint.MethodFingerprint
-import com.android.tools.smali.dexlib2.Opcode
-
-internal object EmptyComponentBuilderFingerprint : MethodFingerprint(
-    opcodes = listOf(
-        Opcode.INVOKE_INTERFACE,
-        Opcode.INVOKE_STATIC_RANGE
-    ),
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentFingerprint.kt
new file mode 100644
index 0000000000..f5dc38971a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.misc.litho.filter.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal object EmptyComponentFingerprint : MethodFingerprint(
+    accessFlags = AccessFlags.PRIVATE or AccessFlags.CONSTRUCTOR,
+    parameters = listOf(),
+    strings = listOf("EmptyComponent"),
+    customFingerprint = { _, classDef ->
+        classDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1
+    }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ReadComponentIdentifierFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ReadComponentIdentifierFingerprint.kt
index 0a6adfda3d..7a86b86ca5 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ReadComponentIdentifierFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ReadComponentIdentifierFingerprint.kt
@@ -1,12 +1,11 @@
 package app.revanced.patches.youtube.misc.litho.filter.fingerprints
 
 import app.revanced.patcher.fingerprint.MethodFingerprint
-import com.android.tools.smali.dexlib2.Opcode
 
+/**
+ * In 19.17 and earlier, this resolves to the same method as [ComponentContextParserFingerprint].
+ * In 19.18+ this resolves to a different method.
+ */
 internal object ReadComponentIdentifierFingerprint : MethodFingerprint(
-    opcodes = listOf(
-        Opcode.IF_NEZ,
-        null,
-        Opcode.MOVE_RESULT_OBJECT // Register stores the component identifier string
-    )
+    strings = listOf("Number of bits must be positive")
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
index ae648899d5..03d19d6cb3 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
@@ -11,8 +11,10 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
 import app.revanced.patches.youtube.misc.navigation.fingerprints.*
 import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
+import app.revanced.util.alsoResolve
 import app.revanced.util.getReference
 import app.revanced.util.indexOfFirstInstructionOrThrow
+import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow
 import app.revanced.util.resultOrThrow
 import com.android.tools.smali.dexlib2.Opcode
 import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
@@ -64,9 +66,7 @@ object NavigationBarHookPatch : BytecodePatch(
             }
         }
 
-        InitializeButtonsFingerprint.apply {
-            resolve(context, PivotBarConstructorFingerprint.resultOrThrow().classDef)
-        }.resultOrThrow().mutableMethod.apply {
+        InitializeButtonsFingerprint.alsoResolve(context, PivotBarConstructorFingerprint).mutableMethod.apply {
             // Hook the current navigation bar enum value. Note, the 'You' tab does not have an enum value.
             val navigationEnumClassName = NavigationEnumFingerprint.resultOrThrow().mutableClass.type
             addHook(Hook.SET_LAST_APP_NAVIGATION_ENUM) {
@@ -121,7 +121,11 @@ object NavigationBarHookPatch : BytecodePatch(
         // Insert before the first ViewGroup method call after inflating,
         // so this works regardless which layout is used.
         ActionBarSearchResultsFingerprint.resultOrThrow().mutableMethod.apply {
-            val instructionIndex = indexOfFirstInstructionOrThrow {
+            val searchBarResourceId = indexOfFirstWideLiteralInstructionValueOrThrow(
+                NavigationBarHookResourcePatch.actionBarSearchResultsViewMicId
+            )
+
+            val instructionIndex = indexOfFirstInstructionOrThrow(searchBarResourceId) {
                 opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "setLayoutDirection"
             }
 
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/ActionBarSearchResultsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/ActionBarSearchResultsFingerprint.kt
index 2ba8106f99..921047d50e 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/ActionBarSearchResultsFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/ActionBarSearchResultsFingerprint.kt
@@ -8,6 +8,5 @@ import com.android.tools.smali.dexlib2.AccessFlags
 internal object ActionBarSearchResultsFingerprint : LiteralValueFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     returnType = "Landroid/view/View;",
-    parameters = listOf("Landroid/view/LayoutInflater;"),
     literalSupplier = { NavigationBarHookResourcePatch.actionBarSearchResultsViewMicId }
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/InitializeButtonsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/InitializeButtonsFingerprint.kt
index f91d1b36f3..eeeb060ffb 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/InitializeButtonsFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/fingerprints/InitializeButtonsFingerprint.kt
@@ -11,6 +11,5 @@ import com.android.tools.smali.dexlib2.AccessFlags
 internal object InitializeButtonsFingerprint : LiteralValueFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     returnType = "V",
-    parameters = listOf(),
     literalSupplier = { NavigationBarHookResourcePatch.imageOnlyTabResourceId }
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/VideoStateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/VideoStateFingerprint.kt
index 7df451a4a3..1f58ddd871 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/VideoStateFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/VideoStateFingerprint.kt
@@ -8,11 +8,8 @@ import com.android.tools.smali.dexlib2.Opcode
 internal object VideoStateFingerprint : MethodFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     returnType = "V",
-    parameters = listOf("L"),
+    parameters = listOf("Lcom/google/android/libraries/youtube/player/features/overlay/controls/ControlsState;"),
     opcodes = listOf(
-        Opcode.IGET_OBJECT,
-        Opcode.INVOKE_VIRTUAL,
-        Opcode.IGET_OBJECT,
         Opcode.CONST_4,
         Opcode.IF_EQZ,
         Opcode.IF_EQZ,
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt
new file mode 100644
index 0000000000..5ef04463c6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt
@@ -0,0 +1,52 @@
+package app.revanced.patches.youtube.misc.playservice
+
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotation.Patch
+import app.revanced.util.findElementByAttributeValueOrThrow
+import kotlin.properties.Delegates
+
+@Patch(description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.")
+internal object VersionCheckPatch : ResourcePatch() {
+
+    var is_19_03_or_greater by Delegates.notNull()
+    var is_19_04_or_greater by Delegates.notNull()
+    var is_19_16_or_greater by Delegates.notNull()
+    var is_19_17_or_greater by Delegates.notNull()
+    var is_19_18_or_greater by Delegates.notNull()
+    var is_19_23_or_greater by Delegates.notNull()
+    var is_19_25_or_greater by Delegates.notNull()
+    var is_19_26_or_greater by Delegates.notNull()
+    var is_19_29_or_greater by Delegates.notNull()
+    var is_19_32_or_greater by Delegates.notNull()
+    var is_19_33_or_greater by Delegates.notNull()
+    var is_19_36_or_greater by Delegates.notNull()
+    var is_19_41_or_greater by Delegates.notNull()
+
+    override fun execute(context: ResourceContext) {
+
+        // The app version is missing from the decompiled manifest,
+        // so instead use the Google Play services version and compare against specific releases.
+        val playStoreServicesVersion = context.document["res/values/integers.xml"].use { document ->
+            document.documentElement.childNodes.findElementByAttributeValueOrThrow(
+                "name",
+                "google_play_services_version"
+            ).textContent.toInt()
+        }
+
+        // All bug fix releases always seem to use the same play store version as the minor version.
+        is_19_03_or_greater = 240402000 <= playStoreServicesVersion
+        is_19_04_or_greater = 240502000 <= playStoreServicesVersion
+        is_19_16_or_greater = 241702000 <= playStoreServicesVersion
+        is_19_17_or_greater = 241802000 <= playStoreServicesVersion
+        is_19_18_or_greater = 241902000 <= playStoreServicesVersion
+        is_19_23_or_greater = 242402000 <= playStoreServicesVersion
+        is_19_25_or_greater = 242599000 <= playStoreServicesVersion
+        is_19_26_or_greater = 242705000 <= playStoreServicesVersion
+        is_19_29_or_greater = 243005000 <= playStoreServicesVersion
+        is_19_32_or_greater = 243199000 <= playStoreServicesVersion
+        is_19_33_or_greater = 243405000 <= playStoreServicesVersion
+        is_19_36_or_greater = 243705000 <= playStoreServicesVersion
+        is_19_41_or_greater = 244305000 <= playStoreServicesVersion
+    }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatch.kt
index 37ccb352f6..de5ecb2063 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatch.kt
@@ -28,27 +28,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
+                "18.38.44",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ]
         )
     ]
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt
index 9e5e4f8b87..575d93e3ef 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt
@@ -21,7 +21,7 @@ internal object RecyclerViewTreeHookPatch : BytecodePatch(
 
         RecyclerViewTreeObserverFingerprint.result?.let {
             it.mutableMethod.apply {
-                val insertIndex = it.scanResult.patternScanResult!!.startIndex
+                val insertIndex = it.scanResult.patternScanResult!!.startIndex + 1
                 val recyclerViewParameter = 2
 
                 addHook = { classDescriptor ->
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/fingerprints/RecyclerViewTreeObserverFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/fingerprints/RecyclerViewTreeObserverFingerprint.kt
index f40fd09c9c..b94c578129 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/fingerprints/RecyclerViewTreeObserverFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/fingerprints/RecyclerViewTreeObserverFingerprint.kt
@@ -9,12 +9,11 @@ internal object RecyclerViewTreeObserverFingerprint : MethodFingerprint(
     returnType = "V",
     accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
     opcodes = listOf(
+        Opcode.CHECK_CAST,
         Opcode.NEW_INSTANCE,
         Opcode.INVOKE_DIRECT,
         Opcode.INVOKE_VIRTUAL,
-        Opcode.NEW_INSTANCE,
-        Opcode.INVOKE_DIRECT,
-        Opcode.IPUT_OBJECT
+        Opcode.NEW_INSTANCE
     ),
     strings = listOf("LithoRVSLCBinder")
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
index 819b9d3b9b..ade624eaf9 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
@@ -16,6 +16,7 @@ import app.revanced.patches.shared.misc.settings.preference.NonInteractivePrefer
 import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
 import app.revanced.patches.shared.misc.settings.preference.TextPreference
 import app.revanced.patches.youtube.misc.check.CheckEnvironmentPatch
+import app.revanced.patches.youtube.misc.fix.cairo.DisableCairoSettingsPatch
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
 import app.revanced.patches.youtube.misc.settings.fingerprints.LicenseActivityOnCreateFingerprint
 import app.revanced.patches.youtube.misc.settings.fingerprints.SetThemeFingerprint
@@ -31,6 +32,7 @@ import java.io.Closeable
         IntegrationsPatch::class,
         SettingsResourcePatch::class,
         AddResourcesPatch::class,
+        DisableCairoSettingsPatch::class,
         // Currently there is no easy way to make a mandatory patch,
         // so for now this is a dependent of this patch.
         CheckEnvironmentPatch::class,
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsResourcePatch.kt
index 28833df23c..a5c5962406 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsResourcePatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsResourcePatch.kt
@@ -1,14 +1,15 @@
 package app.revanced.patches.youtube.misc.settings
 
 import app.revanced.patcher.data.ResourceContext
-import app.revanced.patcher.patch.PatchException
 import app.revanced.patches.all.misc.resources.AddResourcesPatch
 import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
 import app.revanced.patches.shared.misc.settings.BaseSettingsResourcePatch
 import app.revanced.patches.shared.misc.settings.preference.IntentPreference
 import app.revanced.util.ResourceGroup
 import app.revanced.util.copyResources
-import org.w3c.dom.Element
+import app.revanced.util.copyXmlNode
+import app.revanced.util.findElementByAttributeValueOrThrow
+import app.revanced.util.inputStreamFromBundledResource
 
 object SettingsResourcePatch : BaseSettingsResourcePatch(
     IntentPreference(
@@ -37,53 +38,57 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
             context.copyResources("settings", resourceGroup)
         }
 
+        // Copy style properties used to fix over-sized copy menu that appear in EditTextPreference.
+        // For a full explanation of how this fixes the issue, see the comments in this style file
+        // and the comments in the integrations code.
+        val targetResource = "values/styles.xml"
+        inputStreamFromBundledResource(
+            "settings/host",
+            targetResource
+        )!!.let { inputStream ->
+            "resources".copyXmlNode(
+                context.xmlEditor[inputStream],
+                context.xmlEditor["res/${targetResource}"]
+            ).close()
+        }
+
         // Remove horizontal divider from the settings Preferences
         // To better match the appearance of the stock YouTube settings.
         context.xmlEditor["res/values/styles.xml"].use { editor ->
-            val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
+            val document = editor.file
 
-            for (i in 0 until resourcesNode.childNodes.length) {
-                val node = resourcesNode.childNodes.item(i) as? Element ?: continue
-                val name = node.getAttribute("name")
-                if (name == "Theme.YouTube.Settings" || name == "Theme.YouTube.Settings.Dark") {
-                    val listDividerNode = editor.file.createElement("item")
-                    listDividerNode.setAttribute("name", "android:listDivider")
-                    listDividerNode.appendChild(editor.file.createTextNode("@null"))
-                    node.appendChild(listDividerNode)
-                }
+            arrayOf(
+                "Theme.YouTube.Settings",
+                "Theme.YouTube.Settings.Dark"
+            ).forEach { value ->
+                val listDividerNode = document.createElement("item")
+                listDividerNode.setAttribute("name", "android:listDivider")
+                listDividerNode.appendChild(document.createTextNode("@null"))
+
+                document.childNodes.findElementByAttributeValueOrThrow(
+                    "name", value
+                ).appendChild(listDividerNode)
             }
         }
 
         // Modify the manifest and add a data intent filter to the LicenseActivity.
         // Some devices freak out if undeclared data is passed to an intent,
         // and this change appears to fix the issue.
-        var modifiedIntent = false
         context.xmlEditor["AndroidManifest.xml"].use { editor ->
             val document = editor.file
-            // A xml regular-expression would probably work better than this manual searching.
-            val manifestNodes = document.getElementsByTagName("manifest").item(0).childNodes
-            for (i in 0..manifestNodes.length) {
-                val node = manifestNodes.item(i)
-                if (node != null && node.nodeName == "application") {
-                    val applicationNodes = node.childNodes
-                    for (j in 0..applicationNodes.length) {
-                        val applicationChild = applicationNodes.item(j)
-                        if (applicationChild is Element && applicationChild.nodeName == "activity" &&
-                            applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity"
-                        ) {
-                            val intentFilter = document.createElement("intent-filter")
-                            val mimeType = document.createElement("data")
-                            mimeType.setAttribute("android:mimeType", "text/plain")
-                            intentFilter.appendChild(mimeType)
-                            applicationChild.appendChild(intentFilter)
-                            modifiedIntent = true
-                            break
-                        }
-                    }
-                }
-            }
-        }
 
-        if (!modifiedIntent) throw PatchException("Could not modify activity intent")
+            val licenseElement = document.childNodes.findElementByAttributeValueOrThrow(
+                "android:name",
+                "com.google.android.libraries.social.licenses.LicenseActivity"
+            )
+
+            val mimeType = document.createElement("data")
+            mimeType.setAttribute("android:mimeType", "text/plain")
+
+            val intentFilter = document.createElement("intent-filter")
+            intentFilter.appendChild(mimeType)
+
+            licenseElement.appendChild(intentFilter)
+        }
     }
 }
diff --git a/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/HomeActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/HomeActivityFingerprint.kt
deleted file mode 100644
index f54b9ee7c8..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/HomeActivityFingerprint.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package app.revanced.patches.youtube.shared.fingerprints
-
-import app.revanced.patcher.fingerprint.MethodFingerprint
-
-internal object HomeActivityFingerprint : MethodFingerprint(
-    customFingerprint = { methodDef, classDef ->
-        methodDef.name == "onCreate" && classDef.type.endsWith("Shell_HomeActivity;")
-    },
-)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/RollingNumberTextViewAnimationUpdateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/RollingNumberTextViewAnimationUpdateFingerprint.kt
index 14b224eea9..a4eda997e8 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/RollingNumberTextViewAnimationUpdateFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/shared/fingerprints/RollingNumberTextViewAnimationUpdateFingerprint.kt
@@ -25,6 +25,7 @@ internal object RollingNumberTextViewAnimationUpdateFingerprint : MethodFingerpr
         Opcode.INVOKE_VIRTUAL, // set textview padding using bitmap width
     ),
     customFingerprint = { _, classDef ->
-        classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;"
+        classDef.superclass == "Landroid/support/v7/widget/AppCompatTextView;" || classDef.superclass ==
+                "Lcom/google/android/libraries/youtube/rendering/ui/spec/typography/YouTubeAppCompatTextView;"
     }
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/hdrbrightness/HDRBrightnessPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/hdrbrightness/HDRBrightnessPatch.kt
index 31141a3113..437eea94e4 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/hdrbrightness/HDRBrightnessPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/hdrbrightness/HDRBrightnessPatch.kt
@@ -35,7 +35,6 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
                 "19.02.39",
                 "19.03.36",
                 "19.04.38",
-                "19.05.36",
                 "19.06.39",
                 "19.07.40",
                 "19.08.36",
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 88b90ba33e..71c0a2d146 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
@@ -183,9 +183,9 @@ object VideoInformationPatch : BytecodePatch(
         targetClass.interfaces.add(INTEGRATIONS_PLAYER_INTERFACE)
 
         arrayOf(
-            seekToMethod to "seekTo",
-            seekToRelativeMethod to "seekToRelative"
-        ).forEach { (method, name) ->
+            Triple(seekToMethod, "seekTo", true),
+            Triple(seekToRelativeMethod, "seekToRelative", false)
+        ).forEach { (method, name, returnsBoolean) ->
             // Add interface method.
             // Get enum type for the seek helper method.
             val seekSourceEnumType = method.parameterTypes[1].toString()
@@ -194,22 +194,29 @@ object VideoInformationPatch : BytecodePatch(
                 targetClass.type,
                 name,
                 listOf(ImmutableMethodParameter("J", null, "time")),
-                "Z",
+                if (returnsBoolean) "Z" else "V",
                 AccessFlags.PUBLIC or AccessFlags.FINAL,
                 null, null,
                 MutableMethodImplementation(4)
             ).toMutable()
 
-            // Insert helper method instructions.
-            interfaceImplementation.addInstructions(
-                0,
-                """
+            var instructions = """
                     # 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
                 """
+
+            instructions += if (returnsBoolean) """
+                move-result p1
+                return p1                
+            """ else """
+                return-void                
+            """
+
+            // Insert helper method instructions.
+            interfaceImplementation.addInstructions(
+                0,
+                instructions
             )
 
             targetClass.methods.add(interfaceImplementation)
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
index 3fd89abf16..2146a88d47 100644
--- 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
@@ -10,10 +10,9 @@ import com.android.tools.smali.dexlib2.Opcode
  */
 internal object MdxSeekRelativeFingerprint : MethodFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
-    returnType = "Z",
+    // Return type is boolean up to 19.39, and void with 19.39+.
     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/SeekRelativeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/SeekRelativeFingerprint.kt
index 05c89b933e..3580daca87 100644
--- 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
@@ -10,12 +10,10 @@ import com.android.tools.smali.dexlib2.Opcode
  */
 internal object SeekRelativeFingerprint : MethodFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
-    returnType = "Z",
+    // returnType is boolean up to 19.39, and void with 19.39+
     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/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt
index 03711953ab..dbf70a0aac 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt
@@ -1,6 +1,5 @@
 package app.revanced.patches.youtube.video.playerresponse
 
-import app.revanced.util.exception
 import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
@@ -8,21 +7,29 @@ import app.revanced.patcher.patch.BytecodePatch
 import app.revanced.patcher.patch.annotation.Patch
 import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
+import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch
 import app.revanced.patches.youtube.video.playerresponse.fingerprint.PlayerParameterBuilderFingerprint
+import app.revanced.patches.youtube.video.playerresponse.fingerprint.PlayerParameterBuilderLegacyFingerprint
+import app.revanced.util.resultOrThrow
 import java.io.Closeable
 
 @Patch(
-    dependencies = [IntegrationsPatch::class],
+    dependencies = [IntegrationsPatch::class, VersionCheckPatch::class],
 )
 object PlayerResponseMethodHookPatch :
-    BytecodePatch(setOf(PlayerParameterBuilderFingerprint)),
+    BytecodePatch(
+        setOf(
+            PlayerParameterBuilderFingerprint,
+            PlayerParameterBuilderLegacyFingerprint
+        )
+    ),
     Closeable,
     MutableSet by mutableSetOf() {
 
     // Parameter numbers of the patched method.
     private const val PARAMETER_VIDEO_ID = 1
     private const val PARAMETER_PROTO_BUFFER = 3
-    private const val PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11
+    private var PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = -1
 
     // Registers used to pass the parameters to integrations.
     private var playerResponseMethodCopyRegisters = false
@@ -34,8 +41,13 @@ object PlayerResponseMethodHookPatch :
     private var numberOfInstructionsAdded = 0
 
     override fun execute(context: BytecodeContext) {
-        playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod
-            ?: throw PlayerParameterBuilderFingerprint.exception
+        if (VersionCheckPatch.is_19_23_or_greater) {
+            playerResponseMethod = PlayerParameterBuilderFingerprint.resultOrThrow().mutableMethod
+            PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 12
+        } else {
+            playerResponseMethod = PlayerParameterBuilderLegacyFingerprint.resultOrThrow().mutableMethod
+            PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11
+        }
 
         // On some app targets the method has too many registers pushing the parameters past v15.
         // If needed, move the parameters to 4-bit registers so they can be passed to integrations.
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderFingerprint.kt
index 2694940fd6..4152d39aad 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderFingerprint.kt
@@ -4,6 +4,9 @@ import app.revanced.patcher.extensions.or
 import app.revanced.patcher.fingerprint.MethodFingerprint
 import com.android.tools.smali.dexlib2.AccessFlags
 
+/**
+ * For targets 19.25 and later.
+ */
 internal object PlayerParameterBuilderFingerprint : MethodFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     returnType = "L",
@@ -14,6 +17,7 @@ internal object PlayerParameterBuilderFingerprint : MethodFingerprint(
         "Ljava/lang/String;",
         "I",
         "I",
+        "L", // 19.25+ parameter
         "Ljava/util/Set;",
         "Ljava/lang/String;",
         "Ljava/lang/String;",
@@ -21,5 +25,6 @@ internal object PlayerParameterBuilderFingerprint : MethodFingerprint(
         "Z", // Appears to indicate if the video id is being opened or is currently playing.
         "Z",
         "Z"
-    )
+    ),
+    strings = listOf("psps")
 )
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderLegacyFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderLegacyFingerprint.kt
new file mode 100644
index 0000000000..f41a57e344
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/fingerprint/PlayerParameterBuilderLegacyFingerprint.kt
@@ -0,0 +1,28 @@
+package app.revanced.patches.youtube.video.playerresponse.fingerprint
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import com.android.tools.smali.dexlib2.AccessFlags
+
+/**
+ * For targets 19.24 and earlier.
+ */
+internal object PlayerParameterBuilderLegacyFingerprint : MethodFingerprint(
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+    returnType = "L",
+    parameters = listOf(
+        "Ljava/lang/String;", // VideoId.
+        "[B",
+        "Ljava/lang/String;", // Player parameters proto buffer.
+        "Ljava/lang/String;",
+        "I",
+        "I",
+        "Ljava/util/Set;",
+        "Ljava/lang/String;",
+        "Ljava/lang/String;",
+        "L",
+        "Z", // Appears to indicate if the video id is being opened or is currently playing.
+        "Z",
+        "Z"
+    )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt
index dc89623c7a..71849406ab 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt
@@ -35,24 +35,11 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
     compatiblePackages = [
         CompatiblePackage(
             "com.google.android.youtube", [
-                "18.48.39",
+                "18.38.44",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ]
         )
     ]
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt
index 7cdb7d064b..155ccecc50 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt
@@ -21,24 +21,11 @@ import app.revanced.patches.youtube.video.speed.remember.RememberPlaybackSpeedPa
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.48.39",
+                "18.38.44",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ],
         ),
     ],
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt
index 061207c085..bfdf2bfd85 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt
@@ -4,10 +4,10 @@ import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
 import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
+import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
 import app.revanced.patcher.extensions.or
 import app.revanced.patcher.patch.BytecodePatch
-import app.revanced.patcher.patch.PatchException
 import app.revanced.patcher.patch.annotation.Patch
 import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
 import app.revanced.patches.all.misc.resources.AddResourcesPatch
@@ -18,11 +18,14 @@ import app.revanced.patches.youtube.misc.litho.filter.LithoFilterPatch
 import app.revanced.patches.youtube.misc.recyclerviewtree.hook.RecyclerViewTreeHookPatch
 import app.revanced.patches.youtube.misc.settings.SettingsPatch
 import app.revanced.patches.youtube.video.speed.custom.fingerprints.*
-import app.revanced.util.exception
+import app.revanced.util.alsoResolve
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstructionOrThrow
+import app.revanced.util.indexOfFirstWideLiteralInstructionValue
+import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow
+import app.revanced.util.resultOrThrow
 import com.android.tools.smali.dexlib2.AccessFlags
-import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
 import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
-import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
 import com.android.tools.smali.dexlib2.iface.reference.FieldReference
 import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 import com.android.tools.smali.dexlib2.immutable.ImmutableField
@@ -59,82 +62,77 @@ object CustomPlaybackSpeedPatch : BytecodePatch(
             TextPreference("revanced_custom_playback_speeds", inputType = InputType.TEXT_MULTI_LINE)
         )
 
-        val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!!
-        val arrayGenMethodImpl = arrayGenMethod.implementation!!
-
-        val sizeCallIndex = arrayGenMethodImpl.instructions
-            .indexOfFirst { ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "size" }
-
-        if (sizeCallIndex == -1) throw PatchException("Couldn't find call to size()")
-
-        val sizeCallResultRegister =
-            (arrayGenMethodImpl.instructions.elementAt(sizeCallIndex + 1) as OneRegisterInstruction).registerA
-
-        arrayGenMethod.replaceInstruction(
-            sizeCallIndex + 1,
-            "const/4 v$sizeCallResultRegister, 0x0"
-        )
-
-        val (arrayLengthConstIndex, arrayLengthConst) = arrayGenMethodImpl.instructions.withIndex()
-            .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 7 }
-
-        val arrayLengthConstDestination = (arrayLengthConst as OneRegisterInstruction).registerA
-
-        val playbackSpeedsArrayType = "$INTEGRATIONS_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
-
-        arrayGenMethod.addInstructions(
-            arrayLengthConstIndex + 1,
-            """
-                sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
-                array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
-            """
-        )
-
-        val (originalArrayFetchIndex, originalArrayFetch) = arrayGenMethodImpl.instructions.withIndex()
-            .first {
-                val reference = ((it.value as? ReferenceInstruction)?.reference as? FieldReference)
-                reference?.definingClass?.contains("PlayerConfigModel") ?: false &&
-                        reference?.type == "[F"
+        // Replace the speeds float array with custom speeds.
+        SpeedArrayGeneratorFingerprint.resultOrThrow().mutableMethod.apply {
+            val sizeCallIndex = indexOfFirstInstructionOrThrow {
+                getReference()?.name == "size"
+            }
+            val sizeCallResultRegister = getInstruction(
+                sizeCallIndex + 1
+            ).registerA
+
+            replaceInstruction(
+                sizeCallIndex + 1,
+                "const/4 v$sizeCallResultRegister, 0x0"
+            )
+
+            val arrayLengthConstIndex = indexOfFirstWideLiteralInstructionValueOrThrow(7)
+            val arrayLengthConstDestination = getInstruction(
+                arrayLengthConstIndex
+            ).registerA
+            val playbackSpeedsArrayType = "$INTEGRATIONS_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
+
+            addInstructions(
+                arrayLengthConstIndex + 1,
+                """
+                    sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
+                    array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
+                """
+            )
+
+            val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
+                val reference = getReference()
+                reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
+            }
+            val originalArrayFetchDestination = getInstruction(
+                originalArrayFetchIndex
+            ).registerA
+
+            replaceInstruction(
+                originalArrayFetchIndex,
+                "sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType"
+            )
+        }
+
+        // Override the min/max speeds that can be used.
+        SpeedLimiterFingerprint.resultOrThrow().mutableMethod.apply {
+            val limiterMinConstIndex = indexOfFirstWideLiteralInstructionValueOrThrow(
+                0.25f.toRawBits().toLong()
+            )
+            var limiterMaxConstIndex = indexOfFirstWideLiteralInstructionValue(
+                2.0f.toRawBits().toLong()
+            )
+            // Newer targets have 4x max speed.
+            if (limiterMaxConstIndex < 0) {
+                limiterMaxConstIndex = indexOfFirstWideLiteralInstructionValueOrThrow(
+                    4.0f.toRawBits().toLong()
+                )
             }
 
-        val originalArrayFetchDestination = (originalArrayFetch as OneRegisterInstruction).registerA
-
-        arrayGenMethod.replaceInstruction(
-            originalArrayFetchIndex,
-            "sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType"
-        )
-
-        val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!!
-        val limiterMethodImpl = limiterMethod.implementation!!
-
-        val lowerLimitConst = 0.25f.toRawBits()
-        val upperLimitConst = 2.0f.toRawBits()
-        val (limiterMinConstIndex, limiterMinConst) = limiterMethodImpl.instructions.withIndex()
-            .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == lowerLimitConst }
-        val (limiterMaxConstIndex, limiterMaxConst) = limiterMethodImpl.instructions.withIndex()
-            .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == upperLimitConst }
-
-        val limiterMinConstDestination = (limiterMinConst as OneRegisterInstruction).registerA
-        val limiterMaxConstDestination = (limiterMaxConst as OneRegisterInstruction).registerA
-
-        limiterMethod.replaceInstruction(
-            limiterMinConstIndex,
-            "const/high16 v$limiterMinConstDestination, 0x0"
-        )
-        limiterMethod.replaceInstruction(
-            limiterMaxConstIndex,
-            "const/high16 v$limiterMaxConstDestination, 0x41200000  # 10.0f"
-        )
-
-        // region Force old video quality menu.
-        // This is necessary, because there is no known way of adding custom playback speeds to the new menu.
-
-        RecyclerViewTreeHookPatch.addHook(INTEGRATIONS_CLASS_DESCRIPTOR)
+            val limiterMinConstDestination = getInstruction(limiterMinConstIndex).registerA
+            val limiterMaxConstDestination = getInstruction(limiterMaxConstIndex).registerA
 
-        // Required to check if the playback speed menu is currently shown.
-        LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
+            replaceInstruction(
+                limiterMinConstIndex,
+                "const/high16 v$limiterMinConstDestination, 0.0f"
+            )
+            replaceInstruction(
+                limiterMaxConstIndex,
+                "const/high16 v$limiterMaxConstDestination, 10.0f"
+            )
+        }
 
-        GetOldPlaybackSpeedsFingerprint.result?.let { result ->
+        GetOldPlaybackSpeedsFingerprint.resultOrThrow().let { result ->
             // Add a static INSTANCE field to the class.
             // This is later used to call "showOldPlaybackSpeedMenu" on the instance.
             val instanceField = ImmutableField(
@@ -154,13 +152,13 @@ object CustomPlaybackSpeedPatch : BytecodePatch(
 
             // Get the "showOldPlaybackSpeedMenu" method.
             // This is later called on the field INSTANCE.
-            val showOldPlaybackSpeedMenuMethod = ShowOldPlaybackSpeedMenuFingerprint.also {
-                if (!it.resolve(context, result.classDef))
-                    throw ShowOldPlaybackSpeedMenuFingerprint.exception
-            }.result!!.method.toString()
+            val showOldPlaybackSpeedMenuMethod = ShowOldPlaybackSpeedMenuFingerprint.alsoResolve(
+                context,
+                GetOldPlaybackSpeedsFingerprint
+            ).method.toString()
 
             // Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
-            ShowOldPlaybackSpeedMenuIntegrationsFingerprint.result?.mutableMethod?.apply {
+            ShowOldPlaybackSpeedMenuIntegrationsFingerprint.resultOrThrow().mutableMethod.apply {
                 addInstructionsWithLabels(
                     implementation!!.instructions.lastIndex,
                     """
@@ -171,8 +169,16 @@ object CustomPlaybackSpeedPatch : BytecodePatch(
                         invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
                     """
                 )
-            } ?: throw ShowOldPlaybackSpeedMenuIntegrationsFingerprint.exception
-        } ?: throw GetOldPlaybackSpeedsFingerprint.exception
+            }
+        }
+
+        // region Force old video quality menu.
+        // This is necessary, because there is no known way of adding custom playback speeds to the new menu.
+
+        RecyclerViewTreeHookPatch.addHook(INTEGRATIONS_CLASS_DESCRIPTOR)
+
+        // Required to check if the playback speed menu is currently shown.
+        LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
 
         // endregion
     }
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt
index efe99d7afd..a1297ed31d 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/VideoIdPatch.kt
@@ -3,17 +3,22 @@ package app.revanced.patches.youtube.video.videoid
 import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
-import app.revanced.patcher.fingerprint.MethodFingerprint
 import app.revanced.patcher.patch.BytecodePatch
 import app.revanced.patcher.patch.annotation.Patch
 import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
 import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
 import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
+import app.revanced.patches.youtube.video.videoid.fingerprint.VideoIdBackgroundPlayFingerprint
 import app.revanced.patches.youtube.video.videoid.fingerprint.VideoIdFingerprint
-import app.revanced.patches.youtube.video.videoid.fingerprint.VideoIdFingerprintBackgroundPlay
-import app.revanced.util.exception
+import app.revanced.patches.youtube.video.videoid.fingerprint.VideoIdParentFingerprint
+import app.revanced.util.alsoResolve
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstruction
+import app.revanced.util.resultOrThrow
+import com.android.tools.smali.dexlib2.iface.Method
 import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 
 @Patch(
     description = "Hooks to detect when the video id changes",
@@ -21,8 +26,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
 )
 object VideoIdPatch : BytecodePatch(
     setOf(
-        VideoIdFingerprint,
-        VideoIdFingerprintBackgroundPlay
+        VideoIdParentFingerprint,
+        VideoIdBackgroundPlayFingerprint,
     )
 ) {
     private var videoIdRegister = 0
@@ -35,35 +40,28 @@ object VideoIdPatch : BytecodePatch(
 
     override fun execute(context: BytecodeContext) {
 
-        /**
-         * Supplies the method and register index of the video id register.
-         *
-         * @param consumer Consumer that receives the method, insert index and video id register index.
-         */
-        fun MethodFingerprint.setFields(consumer: (MutableMethod, Int, Int) -> Unit) = result?.let { result ->
-            val videoIdRegisterIndex = result.scanResult.patternScanResult!!.endIndex
-
-            result.mutableMethod.let {
-                val videoIdRegister = it.getInstruction(videoIdRegisterIndex).registerA
-                val insertIndex = videoIdRegisterIndex + 1
-                consumer(it, insertIndex, videoIdRegister)
-
-            }
-        } ?: throw exception
-
-        VideoIdFingerprint.setFields { method, index, register ->
-            videoIdMethod = method
-            videoIdInsertIndex = index
-            videoIdRegister = register
+        VideoIdFingerprint.alsoResolve(context, VideoIdParentFingerprint).mutableMethod.apply {
+            videoIdMethod = this
+            val index = indexOfPlayerResponseModelString()
+            videoIdRegister = getInstruction(index + 1).registerA
+            videoIdInsertIndex = index + 2
         }
 
-        VideoIdFingerprintBackgroundPlay.setFields { method, insertIndex, videoIdRegister ->
-            backgroundPlaybackMethod = method
-            backgroundPlaybackInsertIndex = insertIndex
-            backgroundPlaybackVideoIdRegister = videoIdRegister
+        VideoIdBackgroundPlayFingerprint.resultOrThrow().mutableMethod.apply {
+            backgroundPlaybackMethod = this
+            val index = indexOfPlayerResponseModelString()
+            backgroundPlaybackVideoIdRegister = getInstruction(index + 1).registerA
+            backgroundPlaybackInsertIndex = index + 2
         }
     }
 
+    internal fun Method.indexOfPlayerResponseModelString() =
+        indexOfFirstInstruction {
+            val reference = getReference()
+            reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;" &&
+                    reference.returnType == "Ljava/lang/String;"
+        }
+
     /**
      * Hooks the new video id when the video changes.
      *
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdBackgroundPlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdBackgroundPlayFingerprint.kt
new file mode 100644
index 0000000000..afddaca5a9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdBackgroundPlayFingerprint.kt
@@ -0,0 +1,32 @@
+package app.revanced.patches.youtube.video.videoid.fingerprint
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.video.videoid.VideoIdPatch.indexOfPlayerResponseModelString
+import com.android.tools.smali.dexlib2.AccessFlags
+import com.android.tools.smali.dexlib2.Opcode
+
+internal object VideoIdBackgroundPlayFingerprint : MethodFingerprint(
+    returnType = "V",
+    parameters = listOf("L"),
+    opcodes = listOf(
+        Opcode.IF_EQZ,
+        Opcode.INVOKE_INTERFACE,
+        Opcode.MOVE_RESULT_OBJECT,
+        Opcode.IPUT_OBJECT,
+        Opcode.MONITOR_EXIT,
+        Opcode.RETURN_VOID,
+        Opcode.MONITOR_EXIT,
+        Opcode.RETURN_VOID
+    ),
+    // The target snippet of code is buried in a huge switch block and the target method
+    // has been changed many times by YT which makes identifying it more difficult than usual.
+    customFingerprint = { methodDef, classDef ->
+        // Access flags changed in 19.36
+        (methodDef.accessFlags == (AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.DECLARED_SYNCHRONIZED) ||
+            methodDef.accessFlags == (AccessFlags.FINAL or AccessFlags.DECLARED_SYNCHRONIZED)) &&
+                classDef.methods.count() == 17 &&
+                methodDef.implementation != null &&
+                methodDef.indexOfPlayerResponseModelString() >= 0
+    }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprint.kt
index a01b2ebdcc..cb032b18e7 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprint.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprint.kt
@@ -2,6 +2,7 @@ package app.revanced.patches.youtube.video.videoid.fingerprint
 
 import app.revanced.patcher.extensions.or
 import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.video.videoid.VideoIdPatch.indexOfPlayerResponseModelString
 import com.android.tools.smali.dexlib2.AccessFlags
 import com.android.tools.smali.dexlib2.Opcode
 
@@ -10,13 +11,12 @@ internal object VideoIdFingerprint : MethodFingerprint(
     accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
     parameters = listOf("L"),
     opcodes = listOf(
-        Opcode.MOVE_RESULT_OBJECT,
-        Opcode.IF_EQZ,
-        Opcode.IGET_OBJECT,
-        Opcode.IGET_OBJECT,
         Opcode.INVOKE_INTERFACE,
         Opcode.MOVE_RESULT_OBJECT,
         Opcode.INVOKE_INTERFACE,
         Opcode.MOVE_RESULT_OBJECT,
-    )
+    ),
+    customFingerprint = { methodDef, _ ->
+        methodDef.indexOfPlayerResponseModelString() >= 0
+    }
 )
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprintBackgroundPlay.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprintBackgroundPlay.kt
deleted file mode 100644
index 5bc5d4fd3b..0000000000
--- a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdFingerprintBackgroundPlay.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package app.revanced.patches.youtube.video.videoid.fingerprint
-
-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 VideoIdFingerprintBackgroundPlay : MethodFingerprint(
-    returnType = "V",
-    accessFlags = AccessFlags.DECLARED_SYNCHRONIZED or AccessFlags.FINAL or AccessFlags.PUBLIC,
-    parameters = listOf("L"),
-    opcodes = listOf(
-        Opcode.INVOKE_VIRTUAL,
-        Opcode.MOVE_RESULT,
-        Opcode.IF_EQZ,
-        Opcode.IGET_OBJECT,
-        Opcode.IF_EQZ,
-        Opcode.INVOKE_INTERFACE,
-        Opcode.MOVE_RESULT_OBJECT,
-    ),
-)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdParentFingerprint.kt
new file mode 100644
index 0000000000..d91633ed46
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoid/fingerprint/VideoIdParentFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.video.videoid.fingerprint
+
+import app.revanced.patcher.extensions.or
+import app.revanced.util.patch.LiteralValueFingerprint
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal object VideoIdParentFingerprint : LiteralValueFingerprint(
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+    returnType = "[L",
+    parameters = listOf("L"),
+    literalSupplier = { 524288L }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/videoqualitymenu/RestoreOldVideoQualityMenuPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/videoqualitymenu/RestoreOldVideoQualityMenuPatch.kt
index 5e60a69966..8f577d6885 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/video/videoqualitymenu/RestoreOldVideoQualityMenuPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/video/videoqualitymenu/RestoreOldVideoQualityMenuPatch.kt
@@ -29,30 +29,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
         CompatiblePackage(
             "com.google.android.youtube",
             [
-                "18.32.39",
-                "18.37.36",
                 "18.38.44",
-                "18.43.45",
-                "18.44.41",
-                "18.45.43",
-                "18.48.39",
                 "18.49.37",
-                "19.01.34",
-                "19.02.39",
-                "19.03.36",
-                "19.04.38",
-                "19.05.36",
-                "19.06.39",
-                "19.07.40",
-                "19.08.36",
-                "19.09.38",
-                "19.10.39",
-                "19.11.43",
-                "19.12.41",
-                "19.13.37",
-                "19.14.43",
-                "19.15.36",
                 "19.16.39",
+                "19.25.37",
+                "19.34.42",
             ],
         ),
     ],
diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
index 98ec71428e..c9b146645a 100644
--- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
+++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
@@ -3,6 +3,9 @@ package app.revanced.util
 import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
 import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
+import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
+import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
 import app.revanced.patcher.fingerprint.MethodFingerprint
 import app.revanced.patcher.patch.PatchException
 import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
@@ -15,7 +18,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
 import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
 import com.android.tools.smali.dexlib2.iface.reference.Reference
 import com.android.tools.smali.dexlib2.util.MethodUtil
-import org.stringtemplate.v4.compiler.Bytecode.instructions
 
 fun MethodFingerprint.resultOrThrow() = result ?: throw exception
 
@@ -66,6 +68,37 @@ fun MutableMethod.injectHideViewCall(
     "invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
 )
 
+/**
+ * Inserts instructions at a given index, using the existing control flow label at that index.
+ * Inserted instructions can have it's own control flow labels as well.
+ *
+ * Effectively this changes the code from:
+ * :label
+ * (original code)
+ *
+ * Into:
+ * :label
+ * (patch code)
+ * (original code)
+ */
+internal fun MutableMethod.addInstructionsAtControlFlowLabel(
+    insertIndex: Int,
+    instructions: String,
+) {
+    // Duplicate original instruction and add to +1 index.
+    addInstruction(insertIndex + 1, getInstruction(insertIndex))
+
+    // Add patch code at same index as duplicated instruction,
+    // so it uses the original instruction control flow label.
+    addInstructionsWithLabels(insertIndex + 1, instructions)
+
+    // Remove original non duplicated instruction.
+    removeInstruction(insertIndex)
+
+    // Original instruction is now after the inserted patch instructions,
+    // and the original control flow label is on the first instruction of the patch code.
+}
+
 /**
  * Get the index of the first instruction with the id of the given resource name.
  *
@@ -98,6 +131,9 @@ fun Method.indexOfIdResourceOrThrow(resourceName: String): Int {
     return index
 }
 
+// TODO Rename these from 'FirstWideLiteralInstruction' to 'FirstLiteralInstruction',
+// since NarrowLiteralInstruction is a subclass of WideLiteralInstruction.
+
 /**
  * Find the index of the first wide literal instruction with the given value.
  *
@@ -189,6 +225,23 @@ inline fun  Instruction.getReference() = (this as? Refere
 // @Deprecated("Use the overloaded method with an optional start index.", ReplaceWith("indexOfFirstInstruction(predicate)"))
 fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = indexOfFirstInstruction(0, predicate)
 
+/**
+ * @return The index of the first opcode specified, or -1 if not found.
+ * @see indexOfFirstInstructionOrThrow
+ */
+fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int =
+    indexOfFirstInstruction(0, targetOpcode)
+
+/**
+ * @param startIndex Optional starting index to start searching from.
+ * @return The index of the first opcode specified, or -1 if not found.
+ * @see indexOfFirstInstructionOrThrow
+ */
+fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int =
+    indexOfFirstInstruction(startIndex) {
+        opcode == targetOpcode
+    }
+
 /**
  * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
  *
@@ -197,7 +250,11 @@ fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = index
  * @see indexOfFirstInstructionOrThrow
  */
 fun Method.indexOfFirstInstruction(startIndex: Int = 0, predicate: Instruction.() -> Boolean): Int {
-    val index = this.implementation!!.instructions.drop(startIndex).indexOfFirst(predicate)
+    var instructions = this.implementation!!.instructions
+    if (startIndex != 0) {
+        instructions = instructions.drop(startIndex)
+    }
+    val index = instructions.indexOfFirst(predicate)
 
     return if (index >= 0) {
         startIndex + index
@@ -206,6 +263,24 @@ fun Method.indexOfFirstInstruction(startIndex: Int = 0, predicate: Instruction.(
     }
 }
 
+/**
+ * @return The index of the first opcode specified
+ * @throws PatchException
+ * @see indexOfFirstInstruction
+ */
+fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int =
+    indexOfFirstInstructionOrThrow(0, targetOpcode)
+
+/**
+ * @return The index of the first opcode specified, starting from the index specified.
+ * @throws PatchException
+ * @see indexOfFirstInstruction
+ */
+fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int =
+    indexOfFirstInstructionOrThrow(startIndex) {
+        opcode == targetOpcode
+    }
+
 /**
  * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
  *
@@ -218,6 +293,68 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, predicate: Instru
     if (index < 0) {
         throw PatchException("Could not find instruction index")
     }
+
+    return index
+}
+
+/**
+ * Get the index of matching instruction,
+ * starting from and [startIndex] and searching down.
+ *
+ * @param startIndex Optional starting index to search down from. Searching includes the start index.
+ * @return -1 if the instruction is not found.
+ * @see indexOfFirstInstructionReversedOrThrow
+ */
+fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int =
+    indexOfFirstInstructionReversed(startIndex) {
+        opcode == targetOpcode
+    }
+
+/**
+ * Get the index of matching instruction,
+ * starting from and [startIndex] and searching down.
+ *
+ * @param startIndex Optional starting index to search down from. Searching includes the start index.
+ * @return -1 if the instruction is not found.
+ * @see indexOfFirstInstructionReversedOrThrow
+ */
+fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, predicate: Instruction.() -> Boolean): Int {
+    var instructions = this.implementation!!.instructions
+    if (startIndex != null) {
+        instructions = instructions.take(startIndex + 1)
+    }
+
+    return instructions.indexOfLast(predicate)
+}
+
+/**
+ * Get the index of matching instruction,
+ * starting from and [startIndex] and searching down.
+ *
+ * @param startIndex Optional starting index to search down from. Searching includes the start index.
+ * @return -1 if the instruction is not found.
+ * @see indexOfFirstInstructionReversed
+ */
+fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int =
+    indexOfFirstInstructionReversedOrThrow(startIndex) {
+        opcode == targetOpcode
+    }
+
+/**
+ * Get the index of matching instruction,
+ * starting from and [startIndex] and searching down.
+ *
+ * @param startIndex Optional starting index to search down from. Searching includes the start index.
+ * @return -1 if the instruction is not found.
+ * @see indexOfFirstInstructionReversed
+ */
+fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, predicate: Instruction.() -> Boolean): Int {
+    val index = indexOfFirstInstructionReversed(startIndex, predicate)
+
+    if (index < 0) {
+        throw PatchException("Could not find instruction index")
+    }
+
     return index
 }
 
@@ -242,6 +379,26 @@ fun Method.findOpcodeIndicesReversed(filter: Instruction.() -> Boolean): List Unit,
+) {
+    classes.forEach { classDef ->
+        classDef.methods.forEach { method ->
+            method.implementation?.instructions?.forEachIndexed { index, instruction ->
+                if (instruction.opcode == Opcode.CONST &&
+                    (instruction as WideLiteralInstruction).wideLiteral == literal
+                ) {
+                    val mutableMethod = proxy(classDef).mutableClass.findMutableMethodOf(method)
+                    block.invoke(mutableMethod, index)
+                }
+            }
+        }
+    }
+}
 
 /**
  * Return the resolved method early.
diff --git a/src/main/kotlin/app/revanced/util/patch/LiteralValueFingerprint.kt b/src/main/kotlin/app/revanced/util/patch/LiteralValueFingerprint.kt
index 6b1b67174e..db53978bcb 100644
--- a/src/main/kotlin/app/revanced/util/patch/LiteralValueFingerprint.kt
+++ b/src/main/kotlin/app/revanced/util/patch/LiteralValueFingerprint.kt
@@ -28,6 +28,7 @@ abstract class LiteralValueFingerprint(
     parameters = parameters,
     opcodes = opcodes,
     strings = strings,
+    // TODO: add a way for subclasses to also use their own custom fingerprint.
     customFingerprint = { methodDef, _ ->
         methodDef.containsWideLiteralInstructionValue(literalSupplier())
     }
diff --git a/src/main/resources/addresources/values/arrays.xml b/src/main/resources/addresources/values/arrays.xml
index 32751b87be..829f9533e5 100644
--- a/src/main/resources/addresources/values/arrays.xml
+++ b/src/main/resources/addresources/values/arrays.xml
@@ -29,7 +29,7 @@
             
         
         
-            
+            
                 @string/revanced_miniplayer_type_entry_1
                 @string/revanced_miniplayer_type_entry_2
                 @string/revanced_miniplayer_type_entry_3
@@ -37,7 +37,7 @@
                 @string/revanced_miniplayer_type_entry_5
                 @string/revanced_miniplayer_type_entry_6
             
-            
+            
                 
                 ORIGINAL
                 PHONE
@@ -58,30 +58,44 @@
             
         
         
-            
-                @string/revanced_start_page_entry_0
-                @string/revanced_start_page_entry_1
-                @string/revanced_start_page_entry_2
-                @string/revanced_start_page_entry_3
-                @string/revanced_start_page_entry_4
-                @string/revanced_start_page_entry_5
-                @string/revanced_start_page_entry_6
-                @string/revanced_start_page_entry_7
-                @string/revanced_start_page_entry_8
-                @string/revanced_start_page_entry_9
-            
-            
-                
-                MAIN
-                open.search
-                open.subscriptions
-                open.explore
-                open.shorts
-                www.youtube.com/feed/library
-                
-                www.youtube.com/playlist?list=LL
-                www.youtube.com/feed/history
-                www.youtube.com/feed/trending
+            
+                @string/revanced_change_start_page_entry_default
+                @string/revanced_change_start_page_entry_search
+                Shorts 
+                @string/revanced_change_start_page_entry_subscriptions
+                @string/revanced_change_start_page_entry_explore
+                @string/revanced_change_start_page_entry_library
+                @string/revanced_change_start_page_entry_liked_videos
+                @string/revanced_change_start_page_entry_watch_later
+                @string/revanced_change_start_page_entry_history
+                @string/revanced_change_start_page_entry_trending
+                @string/revanced_change_start_page_entry_gaming
+                @string/revanced_change_start_page_entry_live
+                @string/revanced_change_start_page_entry_music
+                @string/revanced_change_start_page_entry_movies
+                @string/revanced_change_start_page_entry_sports
+                @string/revanced_change_start_page_entry_browse
+            
+            
+                
+                ORIGINAL
+                
+                SEARCH
+                SHORTS
+                
+                SUBSCRIPTIONS
+                EXPLORE
+                LIBRARY
+                LIKED_VIDEO
+                WATCH_LATER
+                HISTORY
+                TRENDING
+                GAMING
+                LIVE
+                MUSIC
+                MOVIE
+                SPORTS
+                BROWSE
             
         
         
diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml
index c1625a4e26..00f138b057 100644
--- a/src/main/resources/addresources/values/strings.xml
+++ b/src/main/resources/addresources/values/strings.xml
@@ -972,21 +972,22 @@ This is because Crowdin requires temporarily flattening this file and removing t
             17.33.42 - Restore old UI layout
         
         
-            Set start page
-            Default
-            
-            Home
-            Search
-            
-            Subscriptions
-            Explore
-            Shorts
-            
-            You tab
-            Liked videos
-            
-            History
-            Trending
+            Set start page
+            Default
+            Browse channels
+            Explore
+            Gaming
+            History
+            Library
+            Liked videos
+            Live
+            Movies
+            Music
+            Search
+            Sports
+            Subscriptions
+            Trending
+            Watch later
         
         
             Disable resuming Shorts player
@@ -1009,15 +1010,30 @@ This is because Crowdin requires temporarily flattening this file and removing t
             Modern 1
             Modern 2
             Modern 3
-            Hide expand and close buttons
-            Buttons are hidden\n(swipe miniplayer to expand or close)
-            Expand and close buttons are shown
+            Enable rounded corners
+            Corners are rounded
+            Corners are square
+            Enable double-tap and pinch to resize
+            Double-tap action and pinch to resize is enabled\n\n• Double tap to increase miniplayer size\n• Double tap again to restore original size
+            Double-tap action and pinch to resize is disabled
+            Enable drag and drop
+            Drag and drop is enabled\n\nMiniplayer can be dragged to any corner of the screen
+            Drag and drop is disabled
+            Hide close button
+            Close button is hidden
+            Close button is shown
+            Hide expand and close buttons
+            Buttons are hidden\n\nSwipe to expand or close
+            Expand and close buttons are shown
             Hide subtexts
             Subtexts are hidden
             Subtexts are shown
             Hide skip forward and back buttons
             Skip forward and back are hidden
             Skip forward and back are shown
+            Initial size
+            Initial on screen size, in pixels
+            Pixel size must be between %1$s and %2$s
             Overlay opacity
             Opacity value between 0-100, where 0 is transparent
             Miniplayer overlay opacity must be between 0-100
@@ -1033,7 +1049,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
             Original seekbar color is shown
             Custom seekbar color
             The color of the seekbar
-            Invalid seekbar color value. Using default value.
+            Invalid seekbar color value
         
         
             Bypass image region restrictions
diff --git a/src/main/resources/settings/host/values/styles.xml b/src/main/resources/settings/host/values/styles.xml
new file mode 100644
index 0000000000..7c8412f9f6
--- /dev/null
+++ b/src/main/resources/settings/host/values/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+    
+