fix(YouTube - Change live ring click action): Clicking on the timestamp in the comments opens the channel

This commit is contained in:
inotia00 2025-01-22 13:06:06 +09:00
parent 78f1d962cd
commit da770b32bf
10 changed files with 119 additions and 118 deletions

View File

@ -1,5 +1,9 @@
package app.revanced.extension.youtube.patches.general;
import androidx.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.youtube.patches.general.requests.VideoDetailsRequest;
import app.revanced.extension.youtube.settings.Settings;
@ -10,16 +14,16 @@ public final class OpenChannelOfLiveAvatarPatch {
private static final boolean CHANGE_LIVE_RING_CLICK_ACTION =
Settings.CHANGE_LIVE_RING_CLICK_ACTION.get();
private static volatile String videoId = "";
private static volatile boolean isCommentsPanelOpen = false;
private static final AtomicBoolean engagementPanelOpen = new AtomicBoolean(false);
private static volatile boolean liveChannelAvatarClicked = false;
private static volatile String videoId = "";
public static void commentsPanelClosed() {
isCommentsPanelOpen = false;
public static void showEngagementPanel(@Nullable Object object) {
engagementPanelOpen.set(object != null);
}
public static void commentsPanelOpen() {
isCommentsPanelOpen = true;
public static void hideEngagementPanel() {
engagementPanelOpen.compareAndSet(true, false);
}
public static void liveChannelAvatarClicked() {
@ -34,13 +38,14 @@ public final class OpenChannelOfLiveAvatarPatch {
if (!liveChannelAvatarClicked) {
return false;
}
if (isCommentsPanelOpen) {
if (engagementPanelOpen.get()) {
return false;
}
VideoDetailsRequest request = VideoDetailsRequest.getRequestForVideoId(videoId);
if (request != null) {
String channelId = request.getInfo();
if (channelId != null) {
videoId = "";
liveChannelAvatarClicked = false;
VideoUtils.openChannel(channelId);
return true;
@ -60,7 +65,7 @@ public final class OpenChannelOfLiveAvatarPatch {
if (!liveChannelAvatarClicked) {
return;
}
if (isCommentsPanelOpen) {
if (engagementPanelOpen.get()) {
return;
}
if (newlyLoadedVideoId.isEmpty()) {

View File

@ -7,14 +7,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.emptyComponentLabel
import app.revanced.patches.shared.mainactivity.onCreateMethod
import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.engagementPanelBuilderFingerprint
import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch
import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.FEED_PATH
@ -41,19 +41,16 @@ import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
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.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.util.MethodUtil
private const val CAROUSEL_SHELF_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/CarouselShelfFilter;"
@ -82,6 +79,7 @@ val feedComponentsPatch = bytecodePatch(
sharedResourceIdPatch,
settingsPatch,
bottomSheetHookPatch,
engagementPanelHookPatch,
versionCheckPatch,
)
execute {
@ -176,38 +174,6 @@ val feedComponentsPatch = bytecodePatch(
// region patch for hide relative video
fun Method.indexOfEngagementPanelBuilderInstruction(targetMethod: MutableMethod) =
indexOfFirstInstruction {
opcode == Opcode.INVOKE_DIRECT &&
MethodUtil.methodSignaturesMatch(
targetMethod,
getReference<MethodReference>()!!
)
}
engagementPanelBuilderFingerprint.matchOrThrow().let {
it.classDef.methods.filter { method ->
method.indexOfEngagementPanelBuilderInstruction(it.method) >= 0
}.forEach { method ->
method.apply {
val index = indexOfEngagementPanelBuilderInstruction(it.method)
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
addInstruction(
index + 2,
"invoke-static {v$register}, " +
"$RELATED_VIDEO_CLASS_DESCRIPTOR->showEngagementPanel(Ljava/lang/Object;)V"
)
}
}
}
engagementPanelUpdateFingerprint.methodOrThrow(engagementPanelBuilderFingerprint)
.addInstruction(
0,
"invoke-static {}, $RELATED_VIDEO_CLASS_DESCRIPTOR->hideEngagementPanel()V"
)
linearLayoutManagerItemCountsFingerprint.matchOrThrow().let {
val methodWalker =
it.getWalkerMethod(it.patternMatch!!.endIndex)
@ -224,6 +190,8 @@ val feedComponentsPatch = bytecodePatch(
}
}
hookEngagementPanelState(RELATED_VIDEO_CLASS_DESCRIPTOR)
// endregion
// region patch for hide subscriptions channel section for tablet

View File

@ -11,12 +11,9 @@ import app.revanced.patches.youtube.utils.resourceid.filterBarHeight
import app.revanced.patches.youtube.utils.resourceid.horizontalCardList
import app.revanced.patches.youtube.utils.resourceid.relatedChipCloudMargin
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.reference.MethodReference
internal val breakingNewsFingerprint = legacyFingerprint(
name = "breakingNewsFingerprint",
@ -103,19 +100,6 @@ internal val elementParserParentFingerprint = legacyFingerprint(
strings = listOf("Element tree missing id in debug mode.")
)
internal val engagementPanelUpdateFingerprint = legacyFingerprint(
name = "engagementPanelUpdateFingerprint",
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = listOf("L", "Z"),
customFingerprint = { method, _ ->
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;"
} >= 0
}
)
internal val filterBarHeightFingerprint = legacyFingerprint(
name = "filterBarHeightFingerprint",
returnType = "V",

View File

@ -40,39 +40,3 @@ internal fun indexOfPlaybackStartDescriptorInstruction(method: Method) =
reference?.returnType == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" &&
reference.parameterTypes.isEmpty()
}
internal val engagementPanelCommentsClosedFingerprint = legacyFingerprint(
name = "engagementPanelCommentsClosedFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.IGET_OBJECT,
Opcode.INVOKE_DIRECT,
),
customFingerprint = { method, _ ->
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.name == "hasNext"
} >= 0
}
)
internal val engagementPanelCommentsOpenFingerprint = legacyFingerprint(
name = "engagementPanelCommentsOpenFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.IF_NE,
Opcode.RETURN_VOID,
Opcode.IPUT_OBJECT,
Opcode.RETURN_VOID,
),
customFingerprint = { method, _ ->
method.implementation!!.instructions.count() == 5
}
)

View File

@ -6,7 +6,8 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
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.engagementPanelTitleParentFingerprint
import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch
import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.youtube.utils.patch.PatchList.CHANGE_LIVE_RING_CLICK_ACTION
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
@ -31,30 +32,21 @@ val openChannelOfLiveAvatarPatch = bytecodePatch(
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
playbackStartDescriptorPatch,
sharedResourceIdPatch,
settingsPatch,
sharedResourceIdPatch,
playbackStartDescriptorPatch,
engagementPanelHookPatch,
)
execute {
mapOf(
engagementPanelCommentsClosedFingerprint to "commentsPanelClosed",
engagementPanelCommentsOpenFingerprint to "commentsPanelOpen",
).forEach { (fingerprint, methodName) ->
fingerprint
.methodOrThrow(engagementPanelTitleParentFingerprint)
.addInstruction(
0,
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->$methodName()V"
)
}
elementsImageFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V"
)
hookEngagementPanelState(EXTENSION_CLASS_DESCRIPTOR)
clientSettingEndpointFingerprint.methodOrThrow().apply {
val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ)
var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE)

View File

@ -9,7 +9,6 @@ import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.engagementPanelTitleParentFingerprint
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.patch.PatchList.DESCRIPTION_COMPONENTS

View File

@ -16,6 +16,11 @@ internal val engagementPanelTitleFingerprint = legacyFingerprint(
}
)
internal val engagementPanelTitleParentFingerprint = legacyFingerprint(
name = "engagementPanelTitleParentFingerprint",
strings = listOf("[EngagementPanelTitleHeader] Cannot remove action buttons from header as the child count is out of sync. Buttons to remove exceed current header child count.")
)
internal fun indexOfContentDescriptionInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_VIRTUAL &&

View File

@ -59,11 +59,6 @@ internal val engagementPanelBuilderFingerprint = legacyFingerprint(
)
)
internal val engagementPanelTitleParentFingerprint = legacyFingerprint(
name = "engagementPanelTitleParentFingerprint",
strings = listOf("[EngagementPanelTitleHeader] Cannot remove action buttons from header as the child count is out of sync. Buttons to remove exceed current header child count.")
)
internal val layoutConstructorFingerprint = legacyFingerprint(
name = "layoutConstructorFingerprint",
returnType = "V",

View File

@ -0,0 +1,67 @@
package app.revanced.patches.youtube.utils.engagement
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.utils.engagementPanelBuilderFingerprint
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
private lateinit var hideEngagementPanelMethod: MutableMethod
private var showEngagementPanelMethods = mutableListOf<MutableMethod>()
val engagementPanelHookPatch = bytecodePatch(
description = "engagementPanelHookPatch"
) {
dependsOn(sharedResourceIdPatch)
execute {
engagementPanelBuilderFingerprint.matchOrThrow().let {
it.classDef.methods.filter { method ->
method.indexOfEngagementPanelBuilderInstruction(it.method) >= 0
}.forEach { method ->
showEngagementPanelMethods.add(method)
}
}
hideEngagementPanelMethod =
engagementPanelUpdateFingerprint.methodOrThrow(engagementPanelBuilderFingerprint)
}
}
private fun Method.indexOfEngagementPanelBuilderInstruction(targetMethod: MutableMethod) =
indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_DIRECT &&
MethodUtil.methodSignaturesMatch(
targetMethod,
getReference<MethodReference>()!!
)
}
internal fun hookEngagementPanelState(classDescriptor: String) {
showEngagementPanelMethods.forEach { method ->
method.apply {
val index = indexOfEngagementPanelBuilderInstruction(this)
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
addInstruction(
index + 2,
"invoke-static {v$register}, $classDescriptor->showEngagementPanel(Ljava/lang/Object;)V"
)
}
}
hideEngagementPanelMethod.addInstruction(
0,
"invoke-static {}, $classDescriptor->hideEngagementPanel()V"
)
}

View File

@ -0,0 +1,22 @@
package app.revanced.patches.youtube.utils.engagement
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.reference.MethodReference
internal val engagementPanelUpdateFingerprint = legacyFingerprint(
name = "engagementPanelUpdateFingerprint",
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = listOf("L", "Z"),
customFingerprint = { method, _ ->
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;"
} >= 0
}
)