diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 5ca19d08b..ed2f78f37 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 60eb637c4..b1522e880 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 fd6ffde4b..9cb064b59 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 a36063d3d..4cb81e2fb 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 b2df5d82b..aa3a7f71e 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 cea3988c2..ac5d7499b 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 83b391cb8..88df4beae 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 6c8119fe9..7d2dd4a9a 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 dd3a07ae7..04be05017 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 eaae9af87..e37eb7f38 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 e6ed839e4..5b40947aa 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 000000000..a1c5bf987 --- /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 eee74fde5..1762c2043 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 000000000..99be205e8 --- /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 ddb8bf47d..3989aa2d5 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 b618ea50f..c3af701bf 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 cb33e72a2..98a03b3cb 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 819ea52ca..63cffeb67 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 fabd8cc3a..f5ea00474 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 1da88f35c..f2994c824 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 6446f32a4..161a3a2b3 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 e346945db..bf41baa3b 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 10e8d3604..3ef0caba2 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 2199b05d8..9581f8f8a 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) - - // overriding this parameter register hides the previous and next buttons - val hasNextParameterRegister = callInstruction.startRegister + HAS_NEXT - val hasPreviousParameterRegister = callInstruction.startRegister + HAS_PREVIOUS - - 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 - """ + PlayerControlsPreviousNextOverlayTouchFingerprint.resultOrThrow().mutableMethod.apply { + val resourceIndex = indexOfFirstWideLiteralInstructionValueOrThrow( + HidePlayerButtonsResourcePatch.playerControlPreviousButtonTouchArea ) - } ?: throw PlayerControlsVisibilityModelFingerprint.exception - } - private object ParameterOffsets { - const val HAS_NEXT = 5 - const val HAS_PREVIOUS = 6 + val insertIndex = indexOfFirstInstructionOrThrow(resourceIndex) { + opcode == Opcode.INVOKE_STATIC + && getReference()?.parameterTypes?.firstOrNull() == "Landroid/view/View;" + } + + val viewRegister = getInstruction(insertIndex).registerC + + 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 000000000..7bd23b17a --- /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 000000000..131bf4713 --- /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 4aae5c27e..000000000 --- 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 782999c10..937f2b509 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 51280ac18..9c595da66 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 938d91eb7..e186babd7 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 396e9a8c3..3585f800d 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 37915f48d..7e4ad5123 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 697b081fc..07b842572 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 0d7ccb956..1e596071b 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 9a99f3ef5..3930d1771 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 92b2d5a4f..cd162823e 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 6bf5c58eb..76ac41408 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 0e7b98850..a2fe2ad1c 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 6f369184a..0f4473523 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 e1ba72391..87986185d 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 + 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", - ) + 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) + context.forEachLiteralValueInstruction( + HideShortsComponentsResourcePatch.reelPlayerRightPivotV2Size + ) { literalInstructionIndex -> + val targetIndex = indexOfFirstInstructionOrThrow(literalInstructionIndex) { + getReference()?.name == "getDimensionPixelSize" + } + 1 + + val sizeRegister = getInstruction(targetIndex).registerA + + addInstructions(targetIndex + 1, """ + invoke-static { v$sizeRegister }, $FILTER_CLASS_DESCRIPTOR->getSoundButtonSize(I)I + move-result v$sizeRegister + """ + ) + } + // endregion // region Hide the navigation bar. // Hook to get the pivotBar view. - SetPivotBarVisibilityParentFingerprint.result?.let { - if (!SetPivotBarVisibilityFingerprint.resolve(context, it.classDef)) { - throw SetPivotBarVisibilityFingerprint.exception - } - - 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 - - // Hook to hide the navigation bar when Shorts are being played. - RenderBottomNavigationBarParentFingerprint.result?.let { - if (!RenderBottomNavigationBarFingerprint.resolve(context, it.classDef)) { - throw RenderBottomNavigationBarFingerprint.exception - } - - 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;", + 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 866d8fb19..38a7e99eb 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 12ca03fb1..000000000 --- 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 000000000..de25cf870 --- /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 dece9b371..912aa9704 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 d6d74b1e8..89a0ef942 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 cb606d5c3..00ee226b3 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 000000000..ca9fadf59 --- /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 aee5ad288..ec2802ed2 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 1fee6435e..47b89e353 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 4d7c52a48..58f80a72d 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 + 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") + 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") - ).forEach { (fingerprint, literalValue, methodName) -> - fingerprint.resolve( - context, - MiniplayerModernViewParentFingerprint.resultOrThrow().classDef + Triple( + MiniplayerModernExpandButtonFingerprint, + modernMiniplayerExpand, + "hideMiniplayerExpandClose" + ), + Triple( + MiniplayerModernCloseButtonFingerprint, + modernMiniplayerClose, + "hideMiniplayerExpandClose" + ), + Triple( + MiniplayerModernRewindButtonFingerprint, + modernMiniplayerRewindButton, + "hideMiniplayerRewindForward" + ), + Triple( + MiniplayerModernForwardButtonFingerprint, + modernMiniplayerForwardButton, + "hideMiniplayerRewindForward" + ), + Triple( + MiniplayerModernOverlayViewFingerprint, + scrimOverlay, + "adjustMiniplayerOpacity" ) - - fingerprint.hookInflatedView( + ).forEach { (fingerprint, literalValue, methodName) -> + fingerprint.alsoResolve( + context, + 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 3870f0654..1c8649c09 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" - ] - - modernMiniplayerExpand = ResourceMappingPatch[ - "id", - "modern_miniplayer_expand" - ] - - modernMiniplayerRewindButton = ResourceMappingPatch[ - "id", - "modern_miniplayer_rewind_button" - ] - - modernMiniplayerForwardButton = ResourceMappingPatch[ - "id", - "modern_miniplayer_forward_button" - ] - playerOverlays = ResourceMappingPatch[ "layout", "player_overlays" ] - // 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" - ] + if (VersionCheckPatch.is_19_16_or_greater) { + modernMiniplayerClose = ResourceMappingPatch[ + "id", + "modern_miniplayer_close" + ] + + modernMiniplayerExpand = ResourceMappingPatch[ + "id", + "modern_miniplayer_expand" + ] + + modernMiniplayerRewindButton = ResourceMappingPatch[ + "id", + "modern_miniplayer_rewind_button" + ] + + 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" + ] + + // 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 000000000..134eaed42 --- /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 0afb5e526..1b0c7096e 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 9d9bf5e15..3a1fbfb43 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 c93d718f5..5675204b1 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 eec03ff01..f67bdc9f9 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 8e4556a00..9485bc3d2 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 textViewFieldReference = // Like/Dislike button TextView field - getInstruction(patternResult.endIndex - 1).reference + val isDisLikesBooleanReference = isDisLikesBooleanInstruction.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 75b6eb55c..92ff78829 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 e79cf2729..57569b270 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 54fe00865..ba109ac54 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 a326a6ced..75ec55606 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 2d7066403..6126fda65 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 c8a75eb11..cc029b92c 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 bc5f29ff8..0d4fa777e 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 3aecd97f0..c28aa2564 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 08d3fa027..8d3050c29 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 000000000..6185420bf --- /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 000000000..2d7730fb5 --- /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 85aad5fd0..ea99e000e 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") + 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 - } + // Seekbar drawing + SeekbarOnDrawFingerprint.alsoResolve(context, SeekbarFingerprint).mutableMethod.apply { + // Get left and right of seekbar rectangle. + val moveRectangleToRegisterIndex = indexOfFirstInstructionOrThrow(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 - - val invokeInstruction = instruction as Instruction35c - if ((invokeInstruction.reference as MethodReference).name != "round") continue - - val insertIndex = index + 2 - - // 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 + AppendTimeFingerprint.resultOrThrow().let { + val appendTimePatternScanStartIndex = it.scanResult.patternScanResult!!.startIndex + it.mutableMethod.apply { + val register = getInstruction(appendTimePatternScanStartIndex + 1).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 - """, - ) + 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 + // Set seekbar draw rectangle. + RectangleFieldInvalidatorFingerprint.alsoResolve(context, SeekbarOnDrawFingerprint).mutableMethod.apply { + val fieldIndex = implementation!!.instructions.count() - 3 + val fieldReference = getInstruction(fieldIndex).reference as FieldReference - when (((it as ReferenceInstruction).reference as StringReference).string) { - "replaceMeWithsetSponsorBarRect" -> method.replaceStringInstruction( + // 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 a4aa0f38d..df2fcf9c6 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 e4f42397e..d58e772f1 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 - StartActivityFingerprint.result?.mutableMethod?.addInstruction( + addInstructions( + browseIdIndex + 1, """ + invoke-static { v$browseIdRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$browseIdRegister + """ + ) + } + + // 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 000000000..f3fc189f3 --- /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 746187218..86c227c29 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 303021439..389c2a1cb 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 000000000..21bf07c24 --- /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 8b41fc5df..d5c3aed44 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 23d7a62fd..eb476cf33 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 80cff8a72..98606ab8c 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 095d567c7..f4088bbca 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 d1d9dd39e..8ca354b88 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 1db64983d..a9521378f 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 97fc73d66..c639abb5e 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 7f33326c5..11040d5e1 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 70e1978e7..3c3e2c45a 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 7d24b1086..d816f24a6 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 000000000..b936d7bf9 --- /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 000000000..fc24be05c
--- /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 ffb7aaf67..f97553fc8 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 e74a3d11e..e66efc9c6 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 d85b5f6a1..84e1c061c 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 ed161b254..1c426f6cd 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 ca4ad846e..000000000
--- 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 1b5754067..000000000
--- 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 fbc2ab0db..000000000
--- 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 77eeec32d..000000000
--- 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 196994f49..000000000
--- 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 9148f5fdb..000000000
--- 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 451f7742e..71052a3f3 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 97367d6c1..a19ff03ce 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 c2b3aa1d6..f5766ae30 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 000000000..b05818e4b
--- /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 66fcccaac..f27f23949 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 000000000..e6bfc4cf8
--- /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 4b9620a2a..08c73e9b8 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,104 +80,40 @@ object LithoFilterPatch : BytecodePatch(
      *   }
      * }
      *
-     * class ComponentContextParser {
+     * When patching 19.17 and earlier:
      *
-     *    public ComponentContext parseBytesToComponentContext(...) {
+     * 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 (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
-            }
-        }?.let { bytesToComponentContextMethod ->
 
-            // 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
-
-            // endregion
-
-            // region Hook the method that parses bytes into a ComponentContext.
-
-            val builderMethodIndex = EmptyComponentBuilderFingerprint.patternScanEndIndex
-            val emptyComponentFieldIndex = builderMethodIndex + 2
-
-            bytesToComponentContextMethod.mutableMethod.apply {
-                val insertHookIndex = indexOfFirstInstructionOrThrow {
-                    opcode == Opcode.IPUT_OBJECT &&
-                            getReference()?.type == "Ljava/lang/StringBuilder;"
-                } + 1
-
-                // region Get free registers that this patch uses.
-                // Registers are overwritten right after they are used in this patch, therefore free to clobber.
-
-                val freeRegistersInstruction = getInstruction(insertHookIndex - 2)
-
-                // 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
-
-                @Suppress("UnnecessaryVariable")
-                val stringBuilderRegister = free2
-
-                // endregion
-
-                // region Get references that this patch needs.
-
-                val builderMethodDescriptor = getInstruction(builderMethodIndex).descriptor
-                val emptyComponentFieldDescriptor = getInstruction(emptyComponentFieldIndex).descriptor
-
-                val identifierRegister =
-                    getInstruction(ReadComponentIdentifierFingerprint.patternScanEndIndex).registerA
-
-                // endregion
-
-                // region Patch the method.
-
-                // Insert the instructions that are responsible
-                // to return an EmptyComponent instead of the original component if the filter method returns true.
-                addInstructionsWithLabels(
-                    insertHookIndex,
-                    """
-                        # 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
-                    """,
-                    // 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 {
+        // 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 ->
@@ -181,7 +127,134 @@ object LithoFilterPatch : BytecodePatch(
                     """
                 )
             }
-        } ?: throw LithoFilterFingerprint.exception
+        }
+
+        // region Pass the buffer into Integrations.
+
+        ProtobufBufferReferenceFingerprint.resultOrThrow().mutableMethod.addInstruction(
+            0,
+            " invoke-static { p2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V"
+        )
+
+        // endregion
+
+        // region Hook the method that parses bytes into a ComponentContext.
+
+        var readComponentMethod : Method
+        var builderMethodDescriptor : Method
+        val emptyComponentField : Field
+
+        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
+
+                // 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
+                    }
+
+                    // Result of read component, and also a free register.
+                    val register = getInstruction(
+                        readComponentMethodCallIndex + 1
+                    ).registerA
+
+                    // Insert after 'move-result-object'
+                    val insertHookIndex = readComponentMethodCallIndex + 2
+
+                    // 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
+
+        // region Read component then store the result.
+
+        ReadComponentIdentifierFingerprint.resultOrThrow().let {
+            it.mutableMethod.apply {
+                val insertHookIndex = indexOfFirstInstructionOrThrow {
+                    opcode == Opcode.IPUT_OBJECT &&
+                            getReference()?.type == "Ljava/lang/StringBuilder;"
+                }
+                val stringBuilderRegister = getInstruction(insertHookIndex).registerA
+
+                // 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
+
+                // Find a free temporary register.
+                val register = getInstruction(
+                    // Immediately before is a StringBuilder append constant character.
+                    indexOfFirstInstructionReversedOrThrow(insertHookIndex, Opcode.CONST_16)
+                ).registerA
+
+                // Verify the temp register will not clobber the method result register.
+                if (stringBuilderRegister == register) {
+                    throw PatchException("Free register will clobber StringBuilder register")
+                }
+
+                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
+                """
+
+                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
+                    """
+                    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
+                    """,
+                    ExternalLabel("unfiltered", getInstruction(insertHookIndex))
+                )
+            }
+        }
+
+        // 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 419e0f91c..1249e674c 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 ec6a1f0c6..000000000
--- 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 000000000..f5dc38971
--- /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 0a6adfda3..7a86b86ca 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 ae648899d..03d19d6cb 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 2ba8106f9..921047d50 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 f91d1b36f..eeeb060ff 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 7df451a4a..1f58ddd87 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 000000000..5ef04463c
--- /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 37ccb352f..de5ecb206 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 9e5e4f8b8..575d93e3e 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 f40fd09c9..b94c57812 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 819b9d3b9..ade624eaf 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 28833df23..a5c596240 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 f54b9ee7c..000000000
--- 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 14b224eea..a4eda997e 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 31141a311..437eea94e 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 88b90ba33..71c0a2d14 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 3fd89abf1..2146a88d4 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 05c89b933..3580daca8 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 03711953a..dbf70a0aa 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 2694940fd..4152d39aa 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 000000000..f41a57e34
--- /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 dc89623c7..71849406a 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 7cdb7d064..155ccecc5 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 061207c08..bfdf2bfd8 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!!
+        // Replace the speeds float array with custom speeds.
+        SpeedArrayGeneratorFingerprint.resultOrThrow().mutableMethod.apply {
+            val sizeCallIndex = indexOfFirstInstructionOrThrow {
+                getReference()?.name == "size"
+            }
+            val sizeCallResultRegister = getInstruction(
+                sizeCallIndex + 1
+            ).registerA
 
-        val sizeCallIndex = arrayGenMethodImpl.instructions
-            .indexOfFirst { ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "size" }
+            replaceInstruction(
+                sizeCallIndex + 1,
+                "const/4 v$sizeCallResultRegister, 0x0"
+            )
 
-        if (sizeCallIndex == -1) throw PatchException("Couldn't find call to size()")
+            val arrayLengthConstIndex = indexOfFirstWideLiteralInstructionValueOrThrow(7)
+            val arrayLengthConstDestination = getInstruction(
+                arrayLengthConstIndex
+            ).registerA
+            val playbackSpeedsArrayType = "$INTEGRATIONS_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
 
-        val sizeCallResultRegister =
-            (arrayGenMethodImpl.instructions.elementAt(sizeCallIndex + 1) as OneRegisterInstruction).registerA
+            addInstructions(
+                arrayLengthConstIndex + 1,
+                """
+                    sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
+                    array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
+                """
+            )
 
-        arrayGenMethod.replaceInstruction(
-            sizeCallIndex + 1,
-            "const/4 v$sizeCallResultRegister, 0x0"
-        )
+            val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
+                val reference = getReference()
+                reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
+            }
+            val originalArrayFetchDestination = getInstruction(
+                originalArrayFetchIndex
+            ).registerA
 
-        val (arrayLengthConstIndex, arrayLengthConst) = arrayGenMethodImpl.instructions.withIndex()
-            .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 7 }
+            replaceInstruction(
+                originalArrayFetchIndex,
+                "sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType"
+            )
+        }
 
-        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"
+        // 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
+            val limiterMinConstDestination = getInstruction(limiterMinConstIndex).registerA
+            val limiterMaxConstDestination = getInstruction(limiterMaxConstIndex).registerA
 
-        arrayGenMethod.replaceInstruction(
-            originalArrayFetchIndex,
-            "sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType"
-        )
+            replaceInstruction(
+                limiterMinConstIndex,
+                "const/high16 v$limiterMinConstDestination, 0.0f"
+            )
+            replaceInstruction(
+                limiterMaxConstIndex,
+                "const/high16 v$limiterMaxConstDestination, 10.0f"
+            )
+        }
 
-        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)
-
-        // Required to check if the playback speed menu is currently shown.
-        LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
-
-        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 efe99d7af..a1297ed31 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 000000000..afddaca5a
--- /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 a01b2ebdc..cb032b18e 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 5bc5d4fd3..000000000
--- 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 000000000..d91633ed4
--- /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 5e60a6996..8f577d688 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 98ec71428..c9b146645 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 6b1b67174..db53978bc 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 32751b87b..829f9533e 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
+            
+                @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
             
-            
-                
-                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
+            
+                
+                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 c1625a4e2..00f138b05 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 000000000..7c8412f9f
--- /dev/null
+++ b/src/main/resources/settings/host/values/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+    
+