feat(YouTube/Enable tablet mini player): add Enable modern mini player settings

This commit is contained in:
inotia00 2024-04-29 19:34:41 +09:00
parent 9aecec0df8
commit f66ac24f55
7 changed files with 144 additions and 37 deletions

View File

@ -3,24 +3,35 @@ package app.revanced.patches.youtube.general.tabletminiplayer
import app.revanced.patcher.data.BytecodeContext
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.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.MiniPlayerDimensionsCalculatorFingerprint
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.MiniPlayerOverrideFingerprint
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.MiniPlayerOverrideNoContextFingerprint
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.MiniPlayerResponseModelSizeCheckFingerprint
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.ModernMiniPlayerConfigFingerprint
import app.revanced.patches.youtube.general.tabletminiplayer.fingerprints.ModernMiniPlayerConstructorFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtOutlinePiPWhite
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtOutlineXWhite
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
import app.revanced.util.getReference
import app.revanced.util.getStringInstructionIndex
import app.revanced.util.getTargetIndex
import app.revanced.util.getTargetIndexReversed
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literalInstructionHook
import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@Suppress("unused")
object TabletMiniPlayerPatch : BaseBytecodePatch(
@ -35,7 +46,8 @@ object TabletMiniPlayerPatch : BaseBytecodePatch(
MiniPlayerDimensionsCalculatorFingerprint,
MiniPlayerResponseModelSizeCheckFingerprint,
MiniPlayerOverrideFingerprint,
ModernMiniPlayerConfigFingerprint
ModernMiniPlayerConfigFingerprint,
ModernMiniPlayerConstructorFingerprint
)
) {
override fun execute(context: BytecodeContext) {
@ -57,6 +69,35 @@ object TabletMiniPlayerPatch : BaseBytecodePatch(
hook(it.scanResult.patternScanResult!!.endIndex)
}
}
ModernMiniPlayerConstructorFingerprint.resultOrThrow().let {
it.mutableClass.methods.forEach { mutableMethod ->
mutableMethod.hookModernMiniPlayer()
}
}
// In ModernMiniPlayer, the drawables of the close button and expand button are reversed.
// OnClickListener appears to be applied normally, so this appears to be a bug in YouTube.
// To solve this, swap the drawables of the close and expand buttons.
// This Drawable will be used in multiple Classes, so instead of using LiteralValueFingerprint to patch only specific methods,
// Apply the patch to all methods where literals are used.
mapOf(
YtOutlineXWhite to "replaceCloseButtonDrawableId",
YtOutlinePiPWhite to "replaceExpandButtonDrawableId"
).forEach { (literal, methodName) ->
val smaliInstruction = """
invoke-static {v$REGISTER_TEMPLATE_REPLACEMENT}, $GENERAL_CLASS_DESCRIPTOR->$methodName(I)I
move-result v$REGISTER_TEMPLATE_REPLACEMENT
"""
context.literalInstructionHook(literal, smaliInstruction)
}
SettingsPatch.addPreference(
arrayOf(
"SETTINGS: ENABLE_MODERN_MINI_PLAYER"
)
)
} else {
MiniPlayerOverrideFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
@ -89,14 +130,46 @@ object TabletMiniPlayerPatch : BaseBytecodePatch(
SettingsPatch.updatePatchStatus(this)
}
private fun MutableMethod.hook(index: Int) {
private fun MutableMethod.hook(index: Int) =
hook(index, "enableTabletMiniPlayer")
private fun MutableMethod.hook(
index: Int,
methodName: String
) {
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index, """
invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->enableTabletMiniPlayer(Z)Z
invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->$methodName(Z)Z
move-result v$register
"""
)
}
private fun MutableMethod.hookModernMiniPlayer() {
if (returnType == "Z") {
hook(getTargetIndexReversed(Opcode.RETURN), "enableModernMiniPlayer")
hook(getTargetIndex(Opcode.RETURN), "enableModernMiniPlayer")
}
val iPutIndex = indexOfFirstInstruction {
this.opcode == Opcode.IPUT
&& this.getReference<FieldReference>()?.type == "I"
}
if (iPutIndex < 0) return
val targetReference = getInstruction<ReferenceInstruction>(iPutIndex).reference
val targetInstruction = getInstruction<TwoRegisterInstruction>(iPutIndex)
addInstructions(
iPutIndex + 1, """
invoke-static {v${targetInstruction.registerA}}, $GENERAL_CLASS_DESCRIPTOR->enableModernMiniPlayer(I)I
move-result v${targetInstruction.registerA}
iput v${targetInstruction.registerA}, v${targetInstruction.registerB}, $targetReference
"""
)
removeInstruction(iPutIndex)
}
}

View File

@ -0,0 +1,10 @@
package app.revanced.patches.youtube.general.tabletminiplayer.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object ModernMiniPlayerConstructorFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literalSupplier = { 45623000 }
)

View File

@ -38,8 +38,8 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtWor
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts
import app.revanced.patches.youtube.utils.toolbar.ToolBarHookPatch
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
import app.revanced.util.doRecursively
import app.revanced.util.findMutableMethodOf
import app.revanced.util.getTargetIndex
import app.revanced.util.getTargetIndexWithMethodReferenceName
import app.revanced.util.getTargetIndexWithReference
@ -47,6 +47,7 @@ import app.revanced.util.getTargetIndexWithReferenceReversed
import app.revanced.util.getWalkerMethod
import app.revanced.util.getWideLiteralInstructionIndex
import app.revanced.util.literalInstructionBooleanHook
import app.revanced.util.literalInstructionHook
import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
@ -54,7 +55,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import org.w3c.dom.Element
@ -97,7 +97,17 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
// region patch for change YouTube header
// Invoke YouTube's header attribute into integrations.
replaceHeaderAttributeId(context)
val smaliInstruction = """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->getHeaderAttributeId()I
move-result v$REGISTER_TEMPLATE_REPLACEMENT
"""
arrayOf(
YtPremiumWordMarkHeader,
YtWordMarkHeader
).forEach { literal ->
context.literalInstructionHook(literal, smaliInstruction)
}
// YouTube's headers have the form of AttributeSet, which is decoded from YouTube's built-in classes.
val attributeResolverMethod = AttributeResolverFingerprint.resultOrThrow().mutableMethod
@ -404,36 +414,6 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
SettingsPatch.updatePatchStatus(this)
}
private fun replaceHeaderAttributeId(context: BytecodeContext) {
val headerAttributeIdArray = arrayOf(YtPremiumWordMarkHeader, YtWordMarkHeader)
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
method.implementation.apply {
this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST)
return@forEachIndexed
if (headerAttributeIdArray.indexOf((instruction as Instruction31i).wideLiteral) < 0)
return@forEachIndexed
(instructions.elementAt(index)).apply {
val register = (this as OneRegisterInstruction).registerA
context.proxy(classDef)
.mutableClass
.findMutableMethodOf(method)
.addInstructions(
index + 1, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->getHeaderAttributeId()I
move-result v$register
"""
)
}
}
}
}
}
}
private fun MutableMethod.injectSearchBarHook(
insertIndex: Int,
descriptor: String

View File

@ -91,7 +91,9 @@ object SharedResourceIdPatch : ResourcePatch() {
var VideoQualityBottomSheet = -1L
var VoiceSearch = -1L
var YouTubeControlsOverlaySubtitleButton = -1L
var YtOutlinePiPWhite = -1L
var YtOutlineVideoCamera = -1L
var YtOutlineXWhite = -1L
var YtPremiumWordMarkHeader = -1L
var YtWordMarkHeader = -1L
@ -173,7 +175,9 @@ object SharedResourceIdPatch : ResourcePatch() {
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
VoiceSearch = getId(ID, "voice_search")
YouTubeControlsOverlaySubtitleButton = getId(LAYOUT, "youtube_controls_overlay_subtitle_button")
YtOutlinePiPWhite = getId(DRAWABLE, "yt_outline_picture_in_picture_white_24")
YtOutlineVideoCamera = getId(DRAWABLE, "yt_outline_video_camera_black_24")
YtOutlineXWhite = getId(DRAWABLE, "yt_outline_x_white_24")
YtPremiumWordMarkHeader = getId(ATTR, "ytPremiumWordmarkHeader")
YtWordMarkHeader = getId(ATTR, "ytWordmarkHeader")

View File

@ -24,12 +24,15 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.util.MethodUtil
const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX"
fun MethodFingerprint.resultOrThrow() = result ?: throw exception
/**
@ -136,7 +139,7 @@ fun MethodFingerprint.literalInstructionBooleanHook(
}
}
fun MethodFingerprint.literalInstructionViewHook(
fun MethodFingerprint.literalInstructionHook(
literal: Long,
descriptor: String
) {
@ -152,6 +155,37 @@ fun MethodFingerprint.literalInstructionViewHook(
}
}
fun BytecodeContext.literalInstructionHook(
literal: Long,
smaliInstruction: String
) {
val context = this
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
method.implementation.apply {
this?.instructions?.forEachIndexed { _, instruction ->
if (instruction.opcode != Opcode.CONST)
return@forEachIndexed
if ((instruction as Instruction31i).wideLiteral != literal)
return@forEachIndexed
context.proxy(classDef)
.mutableClass
.findMutableMethodOf(method).apply {
val index = getWideLiteralInstructionIndex(literal)
val register = (instruction as OneRegisterInstruction).registerA.toString()
addInstructions(
index + 1,
smaliInstruction.replace(REGISTER_TEMPLATE_REPLACEMENT, register)
)
}
}
}
}
}
}
/**
* Find the index of the first wide literal instruction with the given value.
*

View File

@ -277,6 +277,10 @@ Limitations:
<string name="revanced_enable_tablet_mini_player_title">Enable tablet mini player</string>
<string name="revanced_enable_tablet_mini_player_summary_on">Tablet mini player is enabled.</string>
<string name="revanced_enable_tablet_mini_player_summary_off">Tablet mini player is disabled.</string>
<string name="revanced_enable_modern_mini_player_title">Enable modern mini player</string>
<string name="revanced_enable_modern_mini_player_summary_on">Modern mini player is enabled.</string>
<string name="revanced_enable_modern_mini_player_summary_off">Modern mini player is disabled.</string>
<string name="revanced_hide_floating_microphone_title">Hide floating microphone button</string>
<string name="revanced_hide_floating_microphone_summary_on">Floating microphone button is hidden.</string>
<string name="revanced_hide_floating_microphone_summary_off">Floating microphone button is shown.</string>

View File

@ -171,6 +171,8 @@
<!-- SETTINGS: ENABLE_TABLET_MINI_PLAYER
<SwitchPreference android:title="@string/revanced_enable_tablet_mini_player_title" android:key="revanced_enable_tablet_mini_player" android:defaultValue="false" android:summaryOn="@string/revanced_enable_tablet_mini_player_summary_on" android:summaryOff="@string/revanced_enable_tablet_mini_player_summary_off" />SETTINGS: ENABLE_TABLET_MINI_PLAYER -->
<!-- SETTINGS: ENABLE_MODERN_MINI_PLAYER
<SwitchPreference android:title="@string/revanced_enable_modern_mini_player_title" android:key="revanced_enable_modern_mini_player" android:defaultValue="false" android:summaryOn="@string/revanced_enable_modern_mini_player_summary_on" android:summaryOff="@string/revanced_enable_modern_mini_player_summary_off" />SETTINGS: ENABLE_MODERN_MINI_PLAYER -->
<!-- SETTINGS: HIDE_LAYOUT_COMPONENTS
<SwitchPreference android:title="@string/revanced_hide_floating_microphone_title" android:key="revanced_hide_floating_microphone" android:defaultValue="true" android:summaryOn="@string/revanced_hide_floating_microphone_summary_on" android:summaryOff="@string/revanced_hide_floating_microphone_summary_off" />