diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 75d89226c..9f2538706 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -1528,6 +1528,12 @@ public final class app/revanced/patches/youtube/layout/hide/time/HideTimestampPa public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + public final class app/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V @@ -1894,6 +1900,7 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat public final class app/revanced/util/BytecodeUtilsKt { public static final fun containsWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z public static final fun findMutableMethodOf (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod; + public static final fun findOpcodeIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List; 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;ILkotlin/jvm/functions/Function1;)I public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)I @@ -1901,6 +1908,7 @@ public final class app/revanced/util/BytecodeUtilsKt { public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)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 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 indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/mapping/ResourceMappingPatch.kt b/src/main/kotlin/app/revanced/patches/shared/misc/mapping/ResourceMappingPatch.kt index 388c4ef61..2c165e1f1 100644 --- a/src/main/kotlin/app/revanced/patches/shared/misc/mapping/ResourceMappingPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/misc/mapping/ResourceMappingPatch.kt @@ -1,6 +1,7 @@ package app.revanced.patches.shared.misc.mapping import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatch import org.w3c.dom.Element import java.util.* @@ -51,9 +52,10 @@ object ResourceMappingPatch : ResourcePatch() { threadPoolExecutor.also { it.shutdown() }.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS) } - operator fun get(type: String, name: String) = resourceMappings.first { - it.type == type && it.name == name - }.id + operator fun get(type: String, name: String) = + resourceMappings.firstOrNull { + it.type == type && it.name == name + }?.id ?: throw PatchException("Could not find resource type: $type name: $name") data class ResourceElement(val type: String, val name: String, val id: Long) } 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 new file mode 100644 index 000000000..4d7c52a48 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt @@ -0,0 +1,370 @@ +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.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.InputType +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.shared.misc.settings.preference.TextPreference +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.modernMiniplayerClose +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.modernMiniplayerExpand +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.modernMiniplayerForwardButton +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.modernMiniplayerRewindButton +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch.scrimOverlay +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.MiniplayerModernAddViewListenerFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernCloseButtonFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernConstructorFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernExpandButtonFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernExpandCloseDrawablesFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernForwardButtonFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernOverlayViewFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernRewindButtonFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerModernViewParentFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerOverrideFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerOverrideNoContextFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.MiniplayerResponseModelSizeCheckFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.YouTubePlayerOverlaysLayoutFingerprint +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.settings.SettingsPatch +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.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. +@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.", + dependencies = [ + IntegrationsPatch::class, + SettingsPatch::class, + AddResourcesPatch::class, + MiniplayerResourcePatch::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 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. + ] + ) + ] +) +@Suppress("unused") +object MiniplayerPatch : BytecodePatch( + setOf(GetFormFactorFingerprint, + MiniplayerDimensionsCalculatorParentFingerprint, + MiniplayerResponseModelSizeCheckFingerprint, + MiniplayerOverrideFingerprint, + MiniplayerModernConstructorFingerprint, + MiniplayerModernViewParentFingerprint, + YouTubePlayerOverlaysLayoutFingerprint + ) +) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/MiniplayerPatch;" + + 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 + + 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) + ) + } + ) + ) + + // region Enable tablet miniplayer. + + MiniplayerOverrideNoContextFingerprint.resolve( + context, + MiniplayerDimensionsCalculatorParentFingerprint.resultOrThrow().classDef + ) + MiniplayerOverrideNoContextFingerprint.resultOrThrow().mutableMethod.apply { + findReturnIndicesReversed().forEach { index -> insertLegacyTabletMiniplayerOverride(index) } + } + + // endregion + + // region Legacy tablet Miniplayer hooks. + + MiniplayerOverrideFingerprint.resultOrThrow().let { + val appNameStringIndex = it.scanResult.stringsScanResult!!.matches.first().index + 2 + + it.mutableMethod.apply { + val walkerMethod = context.toMethodWalker(this) + .nextMethod(appNameStringIndex, true) + .getMethod() as MutableMethod + + walkerMethod.apply { + findReturnIndicesReversed().forEach { index -> insertLegacyTabletMiniplayerOverride(index) } + } + } + } + + MiniplayerResponseModelSizeCheckFingerprint.resultOrThrow().let { + it.mutableMethod.insertLegacyTabletMiniplayerOverride(it.scanResult.patternScanResult!!.endIndex) + } + + if (isPatchingOldVersion) { + // Return here, as patch below is only intended for new versions of the app. + return + } + + // endregion + + + // region Enable modern miniplayer. + + MiniplayerModernConstructorFingerprint.resultOrThrow().mutableClass.methods.forEach { + it.apply { + if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) { + val iPutIndex = indexOfFirstInstructionOrThrow { + this.opcode == Opcode.IPUT && this.getReference()?.type == "I" + } + + insertModernMiniplayerTypeOverride(iPutIndex) + } else { + findReturnIndicesReversed().forEach { index -> insertModernMiniplayerOverride(index) } + } + } + } + + // 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( + context, + MiniplayerModernViewParentFingerprint.resultOrThrow().classDef + ) + }.resultOrThrow().mutableMethod.apply { + listOf( + ytOutlinePictureInPictureWhite24 to ytOutlineXWhite24, + ytOutlineXWhite24 to ytOutlinePictureInPictureWhite24, + ).forEach { (originalResource, replacementResource) -> + val imageResourceIndex = indexOfFirstWideLiteralInstructionValueOrThrow(originalResource) + val register = getInstruction(imageResourceIndex).registerA + + replaceInstruction(imageResourceIndex, "const v$register, $replacementResource") + } + } + + // endregion + + + // region Add hooks to hide tablet 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 + ) + + fingerprint.hookInflatedView( + literalValue, + "Landroid/widget/ImageView;", + "$INTEGRATIONS_CLASS_DESCRIPTOR->$methodName(Landroid/widget/ImageView;)V" + ) + } + + MiniplayerModernAddViewListenerFingerprint.apply { + resolve( + context, + MiniplayerModernViewParentFingerprint.resultOrThrow().classDef + ) + }.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->" + + "hideMiniplayerSubTexts(Landroid/view/View;)V" + ) + + + // Modern 2 has a broken overlay subtitle view that is always present. + // 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. + YouTubePlayerOverlaysLayoutFingerprint.resultOrThrow().mutableClass.methods.add( + ImmutableMethod( + YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME, + "addView", + listOf( + ImmutableMethodParameter("Landroid/view/View;", null, null), + ImmutableMethodParameter("I", null, null), + ImmutableMethodParameter("Landroid/view/ViewGroup\$LayoutParams;", null, null), + ), + "V", + AccessFlags.PUBLIC.value, + null, + null, + MutableMethodImplementation(4), + ).toMutable().apply { + addInstructions( + """ + invoke-super { p0, p1, p2, p3 }, Landroid/view/ViewGroup;->addView(Landroid/view/View;ILandroid/view/ViewGroup${'$'}LayoutParams;)V + invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->playerOverlayGroupCreated(Landroid/view/View;)V + return-void + """, + ) + } + ) + + // endregion + } + + private fun Method.findReturnIndicesReversed() = findOpcodeIndicesReversed(Opcode.RETURN) + + /** + * Adds an override to force legacy tablet miniplayer to be used or not used. + */ + private fun MutableMethod.insertLegacyTabletMiniplayerOverride(index: Int) { + insertBooleanOverride(index, "getLegacyTabletMiniplayerOverride") + } + + /** + * Adds an override to force modern miniplayer to be used or not used. + */ + private fun MutableMethod.insertModernMiniplayerOverride(index: Int) { + insertBooleanOverride(index, "getModernMiniplayerOverride") + } + + private fun MutableMethod.insertBooleanOverride(index: Int, methodName: String) { + val register = getInstruction(index).registerA + addInstructions( + index, + """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->$methodName(Z)Z + move-result v$register + """ + ) + } + + /** + * Adds an override to specify which modern miniplayer is used. + */ + private fun MutableMethod.insertModernMiniplayerTypeOverride(iPutIndex: Int) { + val targetInstruction = getInstruction(iPutIndex) + val targetReference = (targetInstruction as ReferenceInstruction).reference + + addInstructions( + iPutIndex + 1, """ + 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( + 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" + ) + } + } +} 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 new file mode 100644 index 000000000..3870f0654 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerResourcePatch.kt @@ -0,0 +1,81 @@ +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 + +@Patch(dependencies = [ResourceMappingPatch::class]) +internal object MiniplayerResourcePatch : ResourcePatch() { + var floatyBarButtonTopMargin = -1L + + // Only available in 19.15 and upwards. + var ytOutlineXWhite24 = -1L + var ytOutlinePictureInPictureWhite24 = -1L + var scrimOverlay = -1L + var modernMiniplayerClose = -1L + var modernMiniplayerExpand = -1L + var modernMiniplayerRewindButton = -1L + var modernMiniplayerForwardButton = -1L + var playerOverlays = -1L + + override fun execute(context: ResourceContext) { + floatyBarButtonTopMargin = ResourceMappingPatch[ + "dimen", + "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" + ] + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerDimensionsCalculatorParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerDimensionsCalculatorParentFingerprint.kt new file mode 100644 index 000000000..bc156e7af --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerDimensionsCalculatorParentFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object MiniplayerDimensionsCalculatorParentFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("L"), + literalSupplier = { MiniplayerResourcePatch.floatyBarButtonTopMargin } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernAddViewListenerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernAddViewListenerFingerprint.kt new file mode 100644 index 000000000..c4e947240 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernAddViewListenerFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernAddViewListenerFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Landroid/view/View;") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernCloseButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernCloseButtonFingerprint.kt new file mode 100644 index 000000000..e4bbd9aad --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernCloseButtonFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernCloseButtonFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Landroid/widget/ImageView;", + parameters = listOf(), + literalSupplier = { MiniplayerResourcePatch.modernMiniplayerClose } +) \ 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 new file mode 100644 index 000000000..0afb5e526 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +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 diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandButtonFingerprint.kt new file mode 100644 index 000000000..35629e30d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandButtonFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernExpandButtonFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Landroid/widget/ImageView;", + parameters = listOf(), + literalSupplier = { MiniplayerResourcePatch.modernMiniplayerExpand } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandCloseDrawablesFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandCloseDrawablesFingerprint.kt new file mode 100644 index 000000000..9ee56819e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernExpandCloseDrawablesFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernExpandCloseDrawablesFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("L"), + literalSupplier = { MiniplayerResourcePatch.ytOutlinePictureInPictureWhite24 } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernForwardButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernForwardButtonFingerprint.kt new file mode 100644 index 000000000..52053750c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernForwardButtonFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernForwardButtonFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Landroid/widget/ImageView;", + parameters = listOf(), + literalSupplier = { MiniplayerResourcePatch.modernMiniplayerForwardButton } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernOverlayViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernOverlayViewFingerprint.kt new file mode 100644 index 000000000..02730f443 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernOverlayViewFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernOverlayViewFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf(), + literalSupplier = { MiniplayerResourcePatch.scrimOverlay } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernRewindButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernRewindButtonFingerprint.kt new file mode 100644 index 000000000..71b9c054b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernRewindButtonFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Resolves using the class found in [MiniplayerModernViewParentFingerprint]. + */ +internal object MiniplayerModernRewindButtonFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Landroid/widget/ImageView;", + parameters = listOf(), + literalSupplier = { MiniplayerResourcePatch.modernMiniplayerRewindButton } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernViewParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernViewParentFingerprint.kt new file mode 100644 index 000000000..a6baeec70 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerModernViewParentFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object MiniplayerModernViewParentFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Ljava/lang/String;", + parameters = listOf(), + strings = listOf("player_overlay_modern_mini_player_controls") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt similarity index 68% rename from src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt index 9e80c81a2..9d9bf5e15 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideFingerprint.kt @@ -1,10 +1,10 @@ -package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints +package app.revanced.patches.youtube.layout.miniplayer.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -internal object MiniPlayerOverrideFingerprint : MethodFingerprint( +internal object MiniplayerOverrideFingerprint : MethodFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("L"), diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideNoContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideNoContextFingerprint.kt new file mode 100644 index 000000000..53900f993 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerOverrideNoContextFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.layout.miniplayer.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 MiniplayerOverrideNoContextFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + returnType = "Z", + opcodes = listOf(Opcode.IGET_BOOLEAN), // anchor to insert the instruction +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerResponseModelSizeCheckFingerprint.kt similarity index 56% rename from src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerResponseModelSizeCheckFingerprint.kt index 4ac508edc..b25e727cb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/MiniplayerResponseModelSizeCheckFingerprint.kt @@ -1,15 +1,15 @@ -package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints +package app.revanced.patches.youtube.layout.miniplayer.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 MiniPlayerResponseModelSizeCheckFingerprint : MethodFingerprint( - "L", - AccessFlags.PUBLIC or AccessFlags.FINAL, - listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), - listOf( +internal object MiniplayerResponseModelSizeCheckFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "L", + parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), + opcodes = listOf( Opcode.RETURN_OBJECT, Opcode.CHECK_CAST, Opcode.CHECK_CAST, diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/YouTubePlayerOverlaysLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/YouTubePlayerOverlaysLayoutFingerprint.kt new file mode 100644 index 000000000..c53a208e9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/fingerprints/YouTubePlayerOverlaysLayoutFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.miniplayer.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.layout.miniplayer.fingerprints.YouTubePlayerOverlaysLayoutFingerprint.YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME + +internal object YouTubePlayerOverlaysLayoutFingerprint : MethodFingerprint( + customFingerprint = { _, classDef -> + classDef.type == YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME + } +) { + const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME = + "Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;" +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt index fd00bf39b..f64d7d76b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt @@ -1,7 +1,5 @@ package app.revanced.patches.youtube.layout.player.overlay -import app.revanced.util.exception -import app.revanced.util.indexOfFirstWideLiteralInstructionValue import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction @@ -9,6 +7,8 @@ import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.layout.player.overlay.fingerprints.CreatePlayerOverviewFingerprint +import app.revanced.util.exception +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @Patch( @@ -27,7 +27,7 @@ object CustomPlayerOverlayOpacityPatch : BytecodePatch(setOf(CreatePlayerOvervie CreatePlayerOverviewFingerprint.result?.let { result -> result.mutableMethod.apply { val viewRegisterIndex = - indexOfFirstWideLiteralInstructionValue(CustomPlayerOverlayOpacityResourcePatch.scrimOverlayId) + 3 + indexOfFirstWideLiteralInstructionValueOrThrow(CustomPlayerOverlayOpacityResourcePatch.scrimOverlayId) + 3 val viewRegister = getInstruction(viewRegisterIndex).registerA 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 98acf716c..08d3fa027 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,7 +1,5 @@ package app.revanced.patches.youtube.layout.seekbar -import app.revanced.util.exception -import app.revanced.util.indexOfFirstWideLiteralInstructionValue import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction @@ -15,6 +13,8 @@ import app.revanced.patches.youtube.layout.seekbar.fingerprints.ShortsSeekbarCol 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.util.indexOfFirstWideLiteralInstructionValueOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction @@ -30,7 +30,7 @@ internal object SeekbarColorBytecodePatch : BytecodePatch( override fun execute(context: BytecodeContext) { fun MutableMethod.addColorChangeInstructions(resourceId: Long) { - val registerIndex = indexOfFirstWideLiteralInstructionValue(resourceId) + 2 + val registerIndex = indexOfFirstWideLiteralInstructionValueOrThrow(resourceId) + 2 val colorRegister = getInstruction(registerIndex).registerA addInstructions( registerIndex + 1, 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 f09c7d694..8b41fc5df 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 @@ -13,18 +13,51 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.layout.tablet.fingerprints.GetFormFactorFingerprint 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.resultOrThrow @Patch( name = "Enable tablet layout", - description = "Adds an option to spoof the device form factor to a tablet which enables the tablet layout.", - dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], - compatiblePackages = [CompatiblePackage("com.google.android.youtube")] + description = "Adds an option to enable tablet layout", + dependencies = [ + IntegrationsPatch::class, + SettingsPatch::class, + AddResourcesPatch::class, + ], + 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" + ) + ) + ] ) @Suppress("unused") -object EnableTabletLayoutPatch : BytecodePatch( - setOf(GetFormFactorFingerprint) -) { +object EnableTabletLayoutPatch : BytecodePatch(setOf(GetFormFactorFingerprint)) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/TabletLayoutPatch;" + override fun execute(context: BytecodeContext) { AddResourcesPatch(this::class) @@ -32,7 +65,7 @@ object EnableTabletLayoutPatch : BytecodePatch( SwitchPreference("revanced_tablet_layout") ) - GetFormFactorFingerprint.result?.let { + GetFormFactorFingerprint.resultOrThrow().let { it.mutableMethod.apply { val returnIsLargeFormFactorIndex = getInstructions().lastIndex - 4 val returnIsLargeFormFactorLabel = getInstruction(returnIsLargeFormFactorIndex) @@ -40,8 +73,8 @@ object EnableTabletLayoutPatch : BytecodePatch( addInstructionsWithLabels( 0, """ - invoke-static { }, Lapp/revanced/integrations/youtube/patches/EnableTabletLayoutPatch;->enableTabletLayout()Z - move-result v0 # Free register + invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->getTabletLayoutEnabled()Z + move-result v0 if-nez v0, :is_large_form_factor """, ExternalLabel( @@ -50,6 +83,6 @@ object EnableTabletLayoutPatch : BytecodePatch( ) ) } - } ?: GetFormFactorFingerprint.exception + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/fingerprints/GetFormFactorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/fingerprints/GetFormFactorFingerprint.kt index c7cb6d1e7..8742fe5bc 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/fingerprints/GetFormFactorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/fingerprints/GetFormFactorFingerprint.kt @@ -21,5 +21,6 @@ internal object GetFormFactorFingerprint : MethodFingerprint( Opcode.INVOKE_STATIC, Opcode.MOVE_RESULT_OBJECT, Opcode.RETURN_OBJECT - ) + ), + strings = listOf("") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/TabletMiniPlayerPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/TabletMiniPlayerPatch.kt index d3b4183aa..6b1f154bf 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/TabletMiniPlayerPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/TabletMiniPlayerPatch.kt @@ -1,152 +1,11 @@ package app.revanced.patches.youtube.layout.tabletminiplayer import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.fingerprint.MethodFingerprint 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.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.tabletminiplayer.fingerprints.MiniPlayerDimensionsCalculatorParentFingerprint -import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideFingerprint -import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideNoContextFingerprint -import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerResponseModelSizeCheckFingerprint -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.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.patches.youtube.layout.miniplayer.MiniplayerPatch -@Patch( - name = "Tablet mini player", - description = "Adds an option to enable the tablet mini player layout.", - dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], - 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", - ) - ) - ] -) -@Suppress("unused") -object TabletMiniPlayerPatch : BytecodePatch( - setOf( - MiniPlayerDimensionsCalculatorParentFingerprint, - MiniPlayerResponseModelSizeCheckFingerprint, - MiniPlayerOverrideFingerprint - ) -) { +@Deprecated("This patch class has been renamed to Miniplayer.") +object TabletMiniPlayerPatch : BytecodePatch(dependencies = setOf(MiniplayerPatch::class)) { override fun execute(context: BytecodeContext) { - AddResourcesPatch(this::class) - - SettingsPatch.PreferenceScreen.GENERAL_LAYOUT.addPreferences( - SwitchPreference("revanced_tablet_miniplayer") - ) - - // First resolve the fingerprints via the parent fingerprint. - MiniPlayerDimensionsCalculatorParentFingerprint.result - ?: throw MiniPlayerDimensionsCalculatorParentFingerprint.exception - val miniPlayerClass = MiniPlayerDimensionsCalculatorParentFingerprint.result!!.classDef - - /* - * No context parameter method. - */ - MiniPlayerOverrideNoContextFingerprint.resolve(context, miniPlayerClass) - val (method, _, parameterRegister) = MiniPlayerOverrideNoContextFingerprint.addProxyCall() - - // Insert right before the return instruction. - val secondInsertIndex = method.implementation!!.instructions.size - 1 - method.insertOverride( - secondInsertIndex, parameterRegister - /** same register used to return **/ - ) - - /* - * Override every return instruction with the proxy call. - */ - MiniPlayerOverrideFingerprint.result?.let { result -> - result.mutableMethod.let { method -> - val appNameStringIndex = result.scanResult.stringsScanResult!!.matches.first().index + 2 - context.toMethodWalker(method).nextMethod(appNameStringIndex, true) - .getMethod() as MutableMethod - }.apply { - implementation!!.let { implementation -> - val returnIndices = implementation.instructions - .withIndex() - .filter { (_, instruction) -> instruction.opcode == Opcode.RETURN } - .map { (index, _) -> index } - - if (returnIndices.isEmpty()) throw PatchException("No return instructions found.") - - // This method clobbers register p0 to return the value, calculate to override. - val returnedRegister = implementation.registerCount - parameters.size - - // Hook the returned register on every return instruction. - returnIndices.forEach { index -> insertOverride(index, returnedRegister) } - } - } - - return@let - } ?: throw MiniPlayerOverrideFingerprint.exception - - /* - * Size check return value override. - */ - MiniPlayerResponseModelSizeCheckFingerprint.addProxyCall() - } - - // Helper methods. - private fun MethodFingerprint.addProxyCall(): Triple { - val (method, scanIndex, parameterRegister) = this.unwrap() - method.insertOverride(scanIndex, parameterRegister) - - return Triple(method, scanIndex, parameterRegister) - } - - private fun MutableMethod.insertOverride(index: Int, overrideRegister: Int) { - this.addInstructions( - index, - """ - invoke-static {v$overrideRegister}, Lapp/revanced/integrations/youtube/patches/TabletMiniPlayerOverridePatch;->getTabletMiniPlayerOverride(Z)Z - move-result v$overrideRegister - """ - ) - } - - private fun MethodFingerprint.unwrap(): Triple { - val result = this.result!! - val scanIndex = result.scanResult.patternScanResult!!.endIndex - val method = result.mutableMethod - val instructions = method.implementation!!.instructions - val parameterRegister = (instructions[scanIndex] as OneRegisterInstruction).registerA - - return Triple(method, scanIndex, parameterRegister) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorParentFingerprint.kt deleted file mode 100644 index 2e1612373..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorParentFingerprint.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.revanced.patches.youtube.layout.tabletminiplayer.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 MiniPlayerDimensionsCalculatorParentFingerprint : MethodFingerprint( - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "V", - parameters = listOf("F"), - opcodes = listOf( - Opcode.CONST_HIGH16, - Opcode.ADD_FLOAT_2ADDR, - null, // Opcode.MUL_FLOAT or Opcode.MUL_FLOAT_2ADDR - Opcode.CONST_4, - Opcode.INVOKE_STATIC, - Opcode.MOVE_RESULT, - Opcode.FLOAT_TO_INT, - Opcode.INVOKE_INTERFACE, - Opcode.RETURN_VOID, - ) -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt deleted file mode 100644 index c6ea61f3f..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.patches.youtube.layout.tabletminiplayer.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 MiniPlayerOverrideNoContextFingerprint : MethodFingerprint( - "Z", AccessFlags.FINAL or AccessFlags.PRIVATE, - opcodes = listOf(Opcode.RETURN), // anchor to insert the instruction -) \ No newline at end of file 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 3e9bfd603..23d7a62fd 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 @@ -16,7 +16,7 @@ import app.revanced.patches.youtube.layout.theme.fingerprints.UseGradientLoading 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.indexOfFirstWideLiteralInstructionValue +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -123,7 +123,7 @@ object ThemeBytecodePatch : BytecodePatch( ) UseGradientLoadingScreenFingerprint.result?.mutableMethod?.apply { - val isEnabledIndex = indexOfFirstWideLiteralInstructionValue(GRADIENT_LOADING_SCREEN_AB_CONSTANT) + 3 + val isEnabledIndex = indexOfFirstWideLiteralInstructionValueOrThrow(GRADIENT_LOADING_SCREEN_AB_CONSTANT) + 3 val isEnabledRegister = getInstruction(isEnabledIndex - 1).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt index c008f6736..de6994840 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt @@ -286,9 +286,12 @@ object SpoofClientPatch : BytecodePatch( // Fix playback speed menu item if spoofing to iOS. CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let { - val shouldCreateMenuIndex = it.scanResult.patternScanResult!!.endIndex + val scanResult = it.scanResult.patternScanResult!! + if (scanResult.startIndex != 0) throw PatchException("Unexpected start index: ${scanResult.startIndex}") it.mutableMethod.apply { + // Find the conditional check if the playback speed menu item is not created. + val shouldCreateMenuIndex = indexOfFirstInstructionOrThrow(scanResult.endIndex) { opcode == Opcode.IF_EQZ } val shouldCreateMenuRegister = getInstruction(shouldCreateMenuIndex).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlaybackSpeedMenuItemFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlaybackSpeedMenuItemFingerprint.kt index 389977dd8..035771ce2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlaybackSpeedMenuItemFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlaybackSpeedMenuItemFingerprint.kt @@ -8,15 +8,27 @@ import com.android.tools.smali.dexlib2.Opcode internal object CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "V", - parameters = listOf("[L", "F"), opcodes = listOf( - Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, // First instruction of the method Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, Opcode.CONST_4, Opcode.IF_EQZ, Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT, // Return value controls the creation of the playback speed menu item. - Opcode.IF_EQZ, // If the return value is false, the playback speed menu item is not created. + null // MOVE_RESULT or MOVE_RESULT_OBJECT, Return value controls the creation of the playback speed menu item. ), + // 19.01 and earlier is missing the second parameter. + // Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures. + customFingerprint = custom@{ methodDef, _ -> + // 19.01 and earlier parameters are: "[L" + // 19.02+ parameters are "[L", "F" + val parameterTypes = methodDef.parameterTypes + val firstParameter = parameterTypes.firstOrNull() + + if (firstParameter == null || !firstParameter.startsWith("[L")) { + return@custom false + } + + parameterTypes.size == 1 || (parameterTypes.size == 2 && parameterTypes[1] == "F") + } ) diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index acc989c37..7e5388d3f 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -8,6 +8,7 @@ import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch +import com.android.tools.smali.dexlib2.Opcode 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.ReferenceInstruction @@ -99,6 +100,7 @@ fun Method.indexOfIdResourceOrThrow(resourceName: String): Int { * Find the index of the first wide literal instruction with the given value. * * @return the first literal instruction with the value, or -1 if not found. + * @see indexOfFirstWideLiteralInstructionValueOrThrow */ fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let { it.instructions.indexOfFirst { instruction -> @@ -106,6 +108,18 @@ fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementati } } ?: -1 +/** + * Find the index of the first wide literal instruction with the given value, + * or throw an exception if not found. + * + * @return the first literal instruction with the value, or throws [PatchException] if not found. + */ +fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long) : Int { + val index = indexOfFirstWideLiteralInstructionValue(literal) + if (index < 0) throw PatchException("Could not find literal value: $literal") + return index +} + /** * Check if the method contains a literal with the given value. * @@ -144,7 +158,9 @@ inline fun Instruction.getReference() = (this as? Refere * @return The index of the first [Instruction] that matches the predicate. */ // TODO: delete this on next major release, the overloaded method with an optional start index serves the same purposes. -@Deprecated("Use the overloaded method with an optional start index.", ReplaceWith("indexOfFirstInstruction(predicate)")) +// Method is deprecated, but annotation is commented out otherwise during compilation usage of the replacement is +// incorrectly flagged as deprecated. +//@Deprecated("Use the overloaded method with an optional start index.", ReplaceWith("indexOfFirstInstruction(predicate)")) fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = indexOfFirstInstruction(0, predicate) /** @@ -179,6 +195,21 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, predicate: Instru return index } +/** + * @return The list of indices of the opcode in reverse order. + */ +fun Method.findOpcodeIndicesReversed(opcode: Opcode): List { + val indexes = implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> instruction.opcode == opcode } + .map { (index, _) -> index } + .reversed() + + if (indexes.isEmpty()) throw PatchException("No ${opcode.name} instructions found in: $this") + + return indexes +} + /** * Return the resolved methods of [MethodFingerprint]s early. */ diff --git a/src/main/resources/addresources/values/arrays.xml b/src/main/resources/addresources/values/arrays.xml index 5fad2d884..f8f2b4218 100644 --- a/src/main/resources/addresources/values/arrays.xml +++ b/src/main/resources/addresources/values/arrays.xml @@ -16,6 +16,35 @@ 17.33.42 + + + @string/revanced_miniplayer_type_entry_1 + @string/revanced_miniplayer_type_entry_2 + @string/revanced_miniplayer_type_entry_3 + @string/revanced_miniplayer_type_entry_4 + @string/revanced_miniplayer_type_entry_5 + @string/revanced_miniplayer_type_entry_6 + + + + ORIGINAL + PHONE + TABLET + MODERN_1 + MODERN_2 + MODERN_3 + + + @string/revanced_miniplayer_type_entry_1 + @string/revanced_miniplayer_type_entry_2 + @string/revanced_miniplayer_type_entry_3 + + + ORIGINAL + PHONE + TABLET + + @string/revanced_start_page_entry_0 diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml index b4506ea35..774198dbd 100644 --- a/src/main/resources/addresources/values/strings.xml +++ b/src/main/resources/addresources/values/strings.xml @@ -661,6 +661,7 @@ Player overlay opacity Opacity value between 0-100, where 0 is transparent + Player overlay opacity must be between 0-100 Return YouTube Dislike @@ -947,11 +948,29 @@ Tablet layout is enabled Tablet layout is disabled Community posts do not show up on tablet layouts - - - Enable tablet mini player - Mini player is enabled - Mini player is disabled + x + + Miniplayer + Change the style of the in app minimized player + Miniplayer type + Original + Phone + Tablet + 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 + 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 + Overlay opacity + Opacity value between 0-100, where 0 is transparent + Miniplayer overlay opacity must be between 0-100 Enable gradient loading screen