feat(YouTube - Miniplayer): Rename Tablet mini player and allow selecting the style of the in-app miniplayer (#3302)

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
LisoUseInAIKyrios 2024-06-08 01:48:12 +03:00 committed by GitHub
parent 64ac233782
commit 5511736b0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 805 additions and 220 deletions

View file

@ -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 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 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 static final field INSTANCE Lapp/revanced/patches/youtube/layout/panels/popup/PlayerPopupPanelsPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V 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 final class app/revanced/util/BytecodeUtilsKt {
public static final fun containsWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z 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 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 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;ILkotlin/jvm/functions/Function1;)I
public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/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 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 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 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 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 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 public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V

View file

@ -1,6 +1,7 @@
package app.revanced.patches.shared.misc.mapping package app.revanced.patches.shared.misc.mapping
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import org.w3c.dom.Element import org.w3c.dom.Element
import java.util.* import java.util.*
@ -51,9 +52,10 @@ object ResourceMappingPatch : ResourcePatch() {
threadPoolExecutor.also { it.shutdown() }.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS) threadPoolExecutor.also { it.shutdown() }.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
} }
operator fun get(type: String, name: String) = resourceMappings.first { operator fun get(type: String, name: String) =
it.type == type && it.name == name resourceMappings.firstOrNull {
}.id 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) data class ResourceElement(val type: String, val name: String, val id: Long)
} }

View file

@ -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<FieldReference>()?.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<OneRegisterInstruction>(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<OneRegisterInstruction>(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<TwoRegisterInstruction>(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<TypeReference>()?.type == hookedClassType
}
val register = getInstruction<OneRegisterInstruction>(imageViewIndex).registerA
addInstruction(
imageViewIndex + 1,
"invoke-static { v$register }, $integrationsMethodName"
)
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal object MiniPlayerOverrideFingerprint : MethodFingerprint( internal object MiniplayerOverrideFingerprint : MethodFingerprint(
returnType = "L", returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"), parameters = listOf("L"),

View file

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

View file

@ -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.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal object MiniPlayerResponseModelSizeCheckFingerprint : MethodFingerprint( internal object MiniplayerResponseModelSizeCheckFingerprint : MethodFingerprint(
"L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "L",
listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"),
listOf( opcodes = listOf(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CHECK_CAST, Opcode.CHECK_CAST,
Opcode.CHECK_CAST, Opcode.CHECK_CAST,

View file

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

View file

@ -1,7 +1,5 @@
package app.revanced.patches.youtube.layout.player.overlay 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.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction 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.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.layout.player.overlay.fingerprints.CreatePlayerOverviewFingerprint 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 import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch( @Patch(
@ -27,7 +27,7 @@ object CustomPlayerOverlayOpacityPatch : BytecodePatch(setOf(CreatePlayerOvervie
CreatePlayerOverviewFingerprint.result?.let { result -> CreatePlayerOverviewFingerprint.result?.let { result ->
result.mutableMethod.apply { result.mutableMethod.apply {
val viewRegisterIndex = val viewRegisterIndex =
indexOfFirstWideLiteralInstructionValue(CustomPlayerOverlayOpacityResourcePatch.scrimOverlayId) + 3 indexOfFirstWideLiteralInstructionValueOrThrow(CustomPlayerOverlayOpacityResourcePatch.scrimOverlayId) + 3
val viewRegister = val viewRegister =
getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA getInstruction<OneRegisterInstruction>(viewRegisterIndex).registerA

View file

@ -1,7 +1,5 @@
package app.revanced.patches.youtube.layout.seekbar 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.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction 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
import app.revanced.patches.youtube.layout.theme.LithoColorHookPatch.lithoColorOverrideHook import app.revanced.patches.youtube.layout.theme.LithoColorHookPatch.lithoColorOverrideHook
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch 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.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@ -30,7 +30,7 @@ internal object SeekbarColorBytecodePatch : BytecodePatch(
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
fun MutableMethod.addColorChangeInstructions(resourceId: Long) { fun MutableMethod.addColorChangeInstructions(resourceId: Long) {
val registerIndex = indexOfFirstWideLiteralInstructionValue(resourceId) + 2 val registerIndex = indexOfFirstWideLiteralInstructionValueOrThrow(resourceId) + 2
val colorRegister = getInstruction<OneRegisterInstruction>(registerIndex).registerA val colorRegister = getInstruction<OneRegisterInstruction>(registerIndex).registerA
addInstructions( addInstructions(
registerIndex + 1, registerIndex + 1,

View file

@ -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.layout.tablet.fingerprints.GetFormFactorFingerprint
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.exception import app.revanced.util.resultOrThrow
@Patch( @Patch(
name = "Enable tablet layout", name = "Enable tablet layout",
description = "Adds an option to spoof the device form factor to a tablet which enables the tablet layout.", description = "Adds an option to enable tablet layout",
dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], dependencies = [
compatiblePackages = [CompatiblePackage("com.google.android.youtube")] 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") @Suppress("unused")
object EnableTabletLayoutPatch : BytecodePatch( object EnableTabletLayoutPatch : BytecodePatch(setOf(GetFormFactorFingerprint)) {
setOf(GetFormFactorFingerprint) private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/TabletLayoutPatch;"
) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
@ -32,7 +65,7 @@ object EnableTabletLayoutPatch : BytecodePatch(
SwitchPreference("revanced_tablet_layout") SwitchPreference("revanced_tablet_layout")
) )
GetFormFactorFingerprint.result?.let { GetFormFactorFingerprint.resultOrThrow().let {
it.mutableMethod.apply { it.mutableMethod.apply {
val returnIsLargeFormFactorIndex = getInstructions().lastIndex - 4 val returnIsLargeFormFactorIndex = getInstructions().lastIndex - 4
val returnIsLargeFormFactorLabel = getInstruction(returnIsLargeFormFactorIndex) val returnIsLargeFormFactorLabel = getInstruction(returnIsLargeFormFactorIndex)
@ -40,8 +73,8 @@ object EnableTabletLayoutPatch : BytecodePatch(
addInstructionsWithLabels( addInstructionsWithLabels(
0, 0,
""" """
invoke-static { }, Lapp/revanced/integrations/youtube/patches/EnableTabletLayoutPatch;->enableTabletLayout()Z invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->getTabletLayoutEnabled()Z
move-result v0 # Free register move-result v0
if-nez v0, :is_large_form_factor if-nez v0, :is_large_form_factor
""", """,
ExternalLabel( ExternalLabel(
@ -50,6 +83,6 @@ object EnableTabletLayoutPatch : BytecodePatch(
) )
) )
} }
} ?: GetFormFactorFingerprint.exception }
} }
} }

View file

@ -21,5 +21,6 @@ internal object GetFormFactorFingerprint : MethodFingerprint(
Opcode.INVOKE_STATIC, Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT
) ),
strings = listOf("")
) )

View file

@ -1,152 +1,11 @@
package app.revanced.patches.youtube.layout.tabletminiplayer package app.revanced.patches.youtube.layout.tabletminiplayer
import app.revanced.patcher.data.BytecodeContext 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.BytecodePatch
import app.revanced.patcher.patch.PatchException import app.revanced.patches.youtube.layout.miniplayer.MiniplayerPatch
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
@Patch( @Deprecated("This patch class has been renamed to Miniplayer.")
name = "Tablet mini player", object TabletMiniPlayerPatch : BytecodePatch(dependencies = setOf(MiniplayerPatch::class)) {
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
)
) {
override fun execute(context: BytecodeContext) { 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<MutableMethod, Int, Int> {
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<MutableMethod, Int, Int> {
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)
} }
} }

View file

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

View file

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

View file

@ -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.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.exception import app.revanced.util.exception
import app.revanced.util.indexOfFirstWideLiteralInstructionValue import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@ -123,7 +123,7 @@ object ThemeBytecodePatch : BytecodePatch(
) )
UseGradientLoadingScreenFingerprint.result?.mutableMethod?.apply { 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<OneRegisterInstruction>(isEnabledIndex - 1).registerA val isEnabledRegister = getInstruction<OneRegisterInstruction>(isEnabledIndex - 1).registerA
addInstructions( addInstructions(

View file

@ -286,9 +286,12 @@ object SpoofClientPatch : BytecodePatch(
// Fix playback speed menu item if spoofing to iOS. // Fix playback speed menu item if spoofing to iOS.
CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let { 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 { 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<OneRegisterInstruction>(shouldCreateMenuIndex).registerA val shouldCreateMenuRegister = getInstruction<OneRegisterInstruction>(shouldCreateMenuIndex).registerA
addInstructions( addInstructions(

View file

@ -8,15 +8,27 @@ import com.android.tools.smali.dexlib2.Opcode
internal object CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint( internal object CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V", returnType = "V",
parameters = listOf("[L", "F"),
opcodes = listOf( opcodes = listOf(
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, // First instruction of the method
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.IF_EQZ, Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT, // Return value controls the creation of the playback speed menu item. null // MOVE_RESULT or MOVE_RESULT_OBJECT, 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.
), ),
// 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")
}
) )

View file

@ -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.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch 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.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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. * 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. * @return the first literal instruction with the value, or -1 if not found.
* @see indexOfFirstWideLiteralInstructionValueOrThrow
*/ */
fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let { fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let {
it.instructions.indexOfFirst { instruction -> it.instructions.indexOfFirst { instruction ->
@ -106,6 +108,18 @@ fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementati
} }
} ?: -1 } ?: -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. * Check if the method contains a literal with the given value.
* *
@ -144,7 +158,9 @@ inline fun <reified T : Reference> Instruction.getReference() = (this as? Refere
* @return The index of the first [Instruction] that matches the predicate. * @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. // 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) fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = indexOfFirstInstruction(0, predicate)
/** /**
@ -179,6 +195,21 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, predicate: Instru
return index return index
} }
/**
* @return The list of indices of the opcode in reverse order.
*/
fun Method.findOpcodeIndicesReversed(opcode: Opcode): List<Int> {
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. * Return the resolved methods of [MethodFingerprint]s early.
*/ */

View file

@ -16,6 +16,35 @@
<item>17.33.42</item> <item>17.33.42</item>
</string-array> </string-array>
</patch> </patch>
<patch id="layout.miniplayer.MiniplayerPatch">
<string-array name="revanced_miniplayer_type_19_15_entries">
<item>@string/revanced_miniplayer_type_entry_1</item>
<item>@string/revanced_miniplayer_type_entry_2</item>
<item>@string/revanced_miniplayer_type_entry_3</item>
<item>@string/revanced_miniplayer_type_entry_4</item>
<item>@string/revanced_miniplayer_type_entry_5</item>
<item>@string/revanced_miniplayer_type_entry_6</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_15_entry_values">
<!-- Enum names from Integrations. -->
<item>ORIGINAL</item>
<item>PHONE</item>
<item>TABLET</item>
<item>MODERN_1</item>
<item>MODERN_2</item>
<item>MODERN_3</item>
</string-array>
<string-array name="revanced_miniplayer_type_legacy_entries">
<item>@string/revanced_miniplayer_type_entry_1</item>
<item>@string/revanced_miniplayer_type_entry_2</item>
<item>@string/revanced_miniplayer_type_entry_3</item>
</string-array>
<string-array name="revanced_miniplayer_type_legacy_entry_values">
<item>ORIGINAL</item>
<item>PHONE</item>
<item>TABLET</item>
</string-array>
</patch>
<patch id="layout.startpage.ChangeStartPagePatch"> <patch id="layout.startpage.ChangeStartPagePatch">
<string-array name="revanced_start_page_entries"> <string-array name="revanced_start_page_entries">
<item>@string/revanced_start_page_entry_0</item> <item>@string/revanced_start_page_entry_0</item>

View file

@ -661,6 +661,7 @@
<patch id="layout.player.overlay.CustomPlayerOverlayOpacityResourcePatch"> <patch id="layout.player.overlay.CustomPlayerOverlayOpacityResourcePatch">
<string name="revanced_player_overlay_opacity_title">Player overlay opacity</string> <string name="revanced_player_overlay_opacity_title">Player overlay opacity</string>
<string name="revanced_player_overlay_opacity_summary">Opacity value between 0-100, where 0 is transparent</string> <string name="revanced_player_overlay_opacity_summary">Opacity value between 0-100, where 0 is transparent</string>
<string name="revanced_player_overlay_opacity_invalid_toast">Player overlay opacity must be between 0-100</string>
</patch> </patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch"> <patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_settings_title">Return YouTube Dislike</string> <string name="revanced_ryd_settings_title">Return YouTube Dislike</string>
@ -947,11 +948,29 @@
<string name="revanced_tablet_layout_summary_on">Tablet layout is enabled</string> <string name="revanced_tablet_layout_summary_on">Tablet layout is enabled</string>
<string name="revanced_tablet_layout_summary_off">Tablet layout is disabled</string> <string name="revanced_tablet_layout_summary_off">Tablet layout is disabled</string>
<string name="revanced_tablet_layout_user_dialog_message">Community posts do not show up on tablet layouts</string> <string name="revanced_tablet_layout_user_dialog_message">Community posts do not show up on tablet layouts</string>
</patch> </patch>x
<patch id="layout.tabletminiplayer.TabletMiniPlayerPatch"> <patch id="layout.miniplayer.MiniplayerPatch">
<string name="revanced_tablet_miniplayer_title">Enable tablet mini player</string> <string name="revanced_miniplayer_screen_title">Miniplayer</string>
<string name="revanced_tablet_miniplayer_summary_on">Mini player is enabled</string> <string name="revanced_miniplayer_screen_summary">Change the style of the in app minimized player</string>
<string name="revanced_tablet_miniplayer_summary_off">Mini player is disabled</string> <string name="revanced_miniplayer_type_title">Miniplayer type</string>
<string name="revanced_miniplayer_type_entry_1">Original</string>
<string name="revanced_miniplayer_type_entry_2">Phone</string>
<string name="revanced_miniplayer_type_entry_3">Tablet</string>
<string name="revanced_miniplayer_type_entry_4">Modern 1</string>
<string name="revanced_miniplayer_type_entry_5">Modern 2</string>
<string name="revanced_miniplayer_type_entry_6">Modern 3</string>
<string name="revanced_miniplayer_hide_expand_close_title">Hide expand and close buttons</string>
<string name="revanced_miniplayer_hide_expand_close_summary_on">Buttons are hidden\n(swipe miniplayer to expand or close)</string>
<string name="revanced_miniplayer_hide_expand_close_summary_off">Expand and close buttons are shown</string>
<string name="revanced_miniplayer_hide_subtext_title">Hide subtexts</string>
<string name="revanced_miniplayer_hide_subtext_summary_on">Subtexts are hidden</string>
<string name="revanced_miniplayer_hide_subtext_summary_off">Subtexts are shown</string>
<string name="revanced_miniplayer_hide_rewind_forward_title">Hide skip forward and back buttons</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_on">Skip forward and back are hidden</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_off">Skip forward and back are shown</string>
<string name="revanced_miniplayer_opacity_title">Overlay opacity</string>
<string name="revanced_miniplayer_opacity_summary">Opacity value between 0-100, where 0 is transparent</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Miniplayer overlay opacity must be between 0-100</string>
</patch> </patch>
<patch id="layout.theme.ThemeBytecodePatch"> <patch id="layout.theme.ThemeBytecodePatch">
<string name="revanced_gradient_loading_screen_title">Enable gradient loading screen</string> <string name="revanced_gradient_loading_screen_title">Enable gradient loading screen</string>