mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-03 16:14:28 +02:00
feat(YouTube/Enable tablet mini player): add Enable modern mini player
settings
This commit is contained in:
parent
9aecec0df8
commit
f66ac24f55
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
)
|
@ -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
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user