feat(YouTube): Add Open channel of live avatar patch

This commit is contained in:
inotia00
2025-01-18 20:22:09 +09:00
parent 439f4976bc
commit 9b299b41f5
13 changed files with 427 additions and 7 deletions

View File

@ -0,0 +1,42 @@
package app.revanced.patches.youtube.general.channel
import app.revanced.patches.youtube.utils.resourceid.elementsImage
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val elementsImageFingerprint = legacyFingerprint(
name = "elementsImageFingerprint",
returnType = "Landroid/view/View;",
accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
parameters = listOf("Landroid/view/View;"),
literals = listOf(elementsImage),
)
internal val clientSettingEndpointFingerprint = legacyFingerprint(
name = "clientSettingEndpointFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "Ljava/util/Map;"),
strings = listOf(
"force_fullscreen",
"PLAYBACK_START_DESCRIPTOR_MUTATOR",
"VideoPresenterConstants.VIDEO_THUMBNAIL_BITMAP_KEY"
),
customFingerprint = { method, _ ->
indexOfPlaybackStartDescriptorInstruction(method) >= 0
}
)
internal fun indexOfPlaybackStartDescriptorInstruction(method: Method) =
method.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" &&
reference.parameterTypes.isEmpty()
}

View File

@ -0,0 +1,90 @@
package app.revanced.patches.youtube.general.channel
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.youtube.utils.patch.PatchList.OPEN_CHANNEL_OF_LIVE_AVATAR
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch
import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"$GENERAL_PATH/OpenChannelOfLiveAvatarPatch;"
@Suppress("unused")
val layoutSwitchPatch = bytecodePatch(
OPEN_CHANNEL_OF_LIVE_AVATAR.title,
OPEN_CHANNEL_OF_LIVE_AVATAR.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
playbackStartDescriptorPatch,
sharedResourceIdPatch,
settingsPatch,
)
execute {
elementsImageFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V"
)
clientSettingEndpointFingerprint.methodOrThrow().apply {
val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ)
var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE)
var freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
addInstructionsWithLabels(
eqzIndex, """
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar()Z
move-result v$freeRegister
if-eqz v$freeRegister, :ignore
return-void
:ignore
nop
"""
)
val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
freeIndex = indexOfFirstInstructionOrThrow(playbackStartIndex, Opcode.CONST_STRING)
freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
addInstructions(
playbackStartIndex + 1, """
invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference
move-result-object v$freeRegister
invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar(Ljava/lang/String;)V
"""
)
}
// region add settings
addPreference(
arrayOf(
"PREFERENCE_SCREEN: GENERAL",
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
"SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR"
),
OPEN_CHANNEL_OF_LIVE_AVATAR
)
// endregion
}
}

View File

@ -169,6 +169,10 @@ internal enum class PatchList(
"Navigation bar components",
"Adds options to hide or change components related to the navigation bar."
),
OPEN_CHANNEL_OF_LIVE_AVATAR(
"Open channel of live avatar",
"Adds an option to open channel instead of video when clicking on live avatar."
),
OPEN_LINKS_EXTERNALLY(
"Open links externally",
"Adds an option to always open links in your browser instead of in the in-app-browser."

View File

@ -83,6 +83,8 @@ var easySeekEduContainer = -1L
private set
var editSettingsAction = -1L
private set
var elementsImage = -1L
private set
var endScreenElementLayoutCircle = -1L
private set
var endScreenElementLayoutIcon = -1L
@ -383,6 +385,10 @@ internal val sharedResourceIdPatch = resourcePatch(
STRING,
"edit_settings_action"
]
elementsImage = resourceMappings[
ID,
"elements_image"
]
endScreenElementLayoutCircle = resourceMappings[
LAYOUT,
"endscreen_element_layout_circle"

View File

@ -0,0 +1,19 @@
package app.revanced.patches.youtube.video.playbackstart
import app.revanced.util.fingerprint.legacyFingerprint
const val PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR =
"Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;"
/**
* Purpose of this method is not clear, and it's only used to identify
* the obfuscated name of the videoId() method in PlaybackStartDescriptor.
*/
internal val playbackStartFeatureFlagFingerprint = legacyFingerprint(
name = "playbackStartFeatureFlagFingerprint",
returnType = "Z",
parameters = listOf(PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR),
literals = listOf(45380134L)
)

View File

@ -0,0 +1,33 @@
package app.revanced.patches.youtube.video.playbackstart
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
internal lateinit var playbackStartVideoIdReference: Reference
val playbackStartDescriptorPatch = bytecodePatch(
description = "playbackStartDescriptorPatch"
) {
dependsOn(sharedResourceIdPatch)
execute {
// Find the obfuscated method name for PlaybackStartDescriptor.videoId()
playbackStartFeatureFlagFingerprint.methodOrThrow().apply {
val stringMethodIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR
&& reference.returnType == "Ljava/lang/String;"
}
playbackStartVideoIdReference = getInstruction<ReferenceInstruction>(stringMethodIndex).reference
}
}
}