mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 13:17:46 +02:00
feat(YouTube/Enable tablet mini player): add Enable modern mini player
settings
This commit is contained in:
@ -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")
|
||||
|
||||
|
Reference in New Issue
Block a user