feat(YouTube - Hide action buttons): Add setting Hide action button by index, Remove patch option Hide action buttons by index

This commit is contained in:
inotia00
2025-02-07 18:33:08 +09:00
parent 4358a4739b
commit 89920480c7
17 changed files with 591 additions and 249 deletions

View File

@ -1,7 +1,6 @@
package app.revanced.patches.youtube.player.action
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.emptyComponentsFingerprint
@ -10,10 +9,11 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_ACTION_BUTTONS
import app.revanced.patches.youtube.utils.request.buildRequestPatch
import app.revanced.patches.youtube.utils.request.hookBuildRequest
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
@ -44,72 +44,61 @@ val actionButtonsPatch = bytecodePatch(
settingsPatch,
lithoFilterPatch,
videoInformationPatch,
)
val hideActionButtonByIndex by booleanOption(
key = "hideActionButtonByIndex",
default = false,
title = "Hide action buttons by index",
description = """
Add an option to hide action buttons by index.
This setting is still experimental, so use it only for debugging purposes.
""".trimIndentMultiline(),
required = true
buildRequestPatch,
)
execute {
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
var settingArray = arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: HIDE_ACTION_BUTTONS"
)
// region patch for hide action buttons by index
if (hideActionButtonByIndex == true) {
componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply {
val conversionContextToStringMethod =
findMethodOrThrow(parameters[1].type) {
name == "toString"
}
val identifierReference = with (conversionContextToStringMethod) {
val identifierStringIndex =
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
val identifierStringAppendIndex =
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
val identifierAppendIndex =
indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL)
val identifierRegister = getInstruction<FiveRegisterInstruction>(identifierAppendIndex).registerD
val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) {
opcode == Opcode.IGET_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/String;" &&
(this as? TwoRegisterInstruction)?.registerA == identifierRegister
}
getInstruction<ReferenceInstruction>(identifierIndex).reference
componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply {
val conversionContextToStringMethod =
findMethodOrThrow(parameters[1].type) {
name == "toString"
}
val listIndex = implementation!!.instructions.lastIndex
val listRegister = getInstruction<OneRegisterInstruction>(listIndex).registerA
val identifierRegister = listRegister + 1
addInstructionsAtControlFlowLabel(
listIndex, """
move-object/from16 v$identifierRegister, p2
iget-object v$identifierRegister, v$identifierRegister, $identifierReference
invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
move-result-object v$listRegister
"""
)
settingArray += "SETTINGS: HIDE_BUTTONS_BY_INDEX"
val identifierReference = with (conversionContextToStringMethod) {
val identifierStringIndex =
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
val identifierStringAppendIndex =
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
val identifierAppendIndex =
indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL)
val identifierRegister = getInstruction<FiveRegisterInstruction>(identifierAppendIndex).registerD
val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) {
opcode == Opcode.IGET_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/String;" &&
(this as? TwoRegisterInstruction)?.registerA == identifierRegister
}
getInstruction<ReferenceInstruction>(identifierIndex).reference
}
val listIndex = implementation!!.instructions.lastIndex
val listRegister = getInstruction<OneRegisterInstruction>(listIndex).registerA
val identifierRegister = listRegister + 1
addInstructionsAtControlFlowLabel(
listIndex, """
move-object/from16 v$identifierRegister, p2
iget-object v$identifierRegister, v$identifierRegister, $identifierReference
invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
move-result-object v$listRegister
"""
)
}
hookBuildRequest("$ACTION_BUTTONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V")
// endregion
// region add settings
addPreference(
settingArray,
arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: HIDE_ACTION_BUTTONS"
),
HIDE_ACTION_BUTTONS
)

View File

@ -33,38 +33,6 @@ internal val buildMediaDataSourceFingerprint = legacyFingerprint(
)
)
internal val buildRequestFingerprint = legacyFingerprint(
name = "buildRequestFingerprint",
customFingerprint = { method, _ ->
method.implementation != null &&
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
!method.definingClass.startsWith("Lorg/") &&
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
// Earlier targets
(indexOfEntrySetInstruction(method) >= 0 ||
// Later targets
method.parameters[1].type == "Ljava/util/Map;")
}
)
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setRequestFinishedListener"
}
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;"
}
internal fun indexOfEntrySetInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
}
internal val createStreamingDataFingerprint = legacyFingerprint(
name = "createStreamingDataFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,

View File

@ -18,6 +18,8 @@ import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
import app.revanced.patches.youtube.utils.request.buildRequestPatch
import app.revanced.patches.youtube.utils.request.hookBuildRequest
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.findInstructionIndicesReversedOrThrow
@ -31,7 +33,6 @@ import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
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
@ -39,7 +40,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
const val EXTENSION_CLASS_DESCRIPTOR =
private const val EXTENSION_CLASS_DESCRIPTOR =
"$SPOOF_PATH/SpoofStreamingDataPatch;"
val spoofStreamingDataPatch = bytecodePatch(
@ -52,36 +53,14 @@ val spoofStreamingDataPatch = bytecodePatch(
settingsPatch,
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
blockRequestPatch,
buildRequestPatch,
)
execute {
// region Get replacement streams at player requests.
buildRequestFingerprint.methodOrThrow().apply {
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
val urlRegister =
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
val entrySetIndex = indexOfEntrySetInstruction(this)
val mapRegister = if (entrySetIndex < 0)
urlRegister + 1
else
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
var smaliInstructions =
"invoke-static { v$urlRegister, v$mapRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->" +
"fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
if (entrySetIndex < 0) smaliInstructions = """
move-object/from16 v$mapRegister, p1
""" + smaliInstructions
// Copy request headers for streaming data fetch.
addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
}
hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V")
// endregion

View File

@ -0,0 +1,56 @@
package app.revanced.patches.youtube.utils.request
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
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.extension.sharedExtensionPatch
import app.revanced.util.fingerprint.methodOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
private lateinit var buildRequestMethod: MutableMethod
private var urlRegister = 0
private var mapRegister = 0
private var offSet = 0
val buildRequestPatch = bytecodePatch(
description = "buildRequestPatch",
) {
dependsOn(sharedExtensionPatch)
execute {
buildRequestFingerprint.methodOrThrow().apply {
buildRequestMethod = this
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
urlRegister =
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
val entrySetIndex = indexOfEntrySetInstruction(this)
val isLegacyTarget = entrySetIndex < 0
mapRegister = if (isLegacyTarget)
urlRegister + 1
else
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
if (isLegacyTarget) {
addInstructions(
newRequestBuilderIndex + 2,
"move-object/from16 v$mapRegister, p1"
)
offSet++
}
}
}
}
internal fun hookBuildRequest(descriptor: String) {
buildRequestMethod.apply {
val insertIndex = indexOfNewUrlRequestBuilderInstruction(this) + 2 + offSet
addInstructions(
insertIndex,
"invoke-static { v$urlRegister, v$mapRegister }, $descriptor"
)
}
}

View File

@ -0,0 +1,40 @@
package app.revanced.patches.youtube.utils.request
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
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 buildRequestFingerprint = legacyFingerprint(
name = "buildRequestFingerprint",
customFingerprint = { method, _ ->
method.implementation != null &&
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
!method.definingClass.startsWith("Lorg/") &&
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
// Earlier targets
(indexOfEntrySetInstruction(method) >= 0 ||
// Later targets
method.parameters[1].type == "Ljava/util/Map;")
}
)
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setRequestFinishedListener"
}
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;"
}
internal fun indexOfEntrySetInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
}