use shared litho patch

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
This commit is contained in:
rospino74 2026-02-04 12:30:56 +01:00 committed by GitHub
parent 58e141f735
commit b57dbaf1b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 114 additions and 296 deletions

View file

@ -15,8 +15,8 @@ import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
/**
* Allows custom filtering using a path and optionally a proto buffer string.
@ -147,7 +147,7 @@ public final class CustomFilter extends Filter {
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
if (custom.startsWith && contentIndex != 0) {

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.litho;
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -11,12 +11,10 @@ import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
@SuppressWarnings("unused")
public final class LithoFilterPatch {
@ -39,11 +37,11 @@ public final class LithoFilterPatch {
public String toString() {
// Estimate the percentage of the buffer that are Strings.
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
builder.append( "ID: ");
builder.append("ID: ");
builder.append(identifier);
builder.append(" Path: ");
builder.append(path);
if (Settings.DEBUG_PROTOBUFFER.get()) {
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: ");
findAsciiStrings(builder, buffer);
}
@ -83,9 +81,10 @@ public final class LithoFilterPatch {
/**
* Placeholder for actual filters.
*/
private static final class DummyFilter extends Filter { }
private static final class DummyFilter extends Filter {
}
private static final Filter[] filters = new Filter[] {
private static final Filter[] filters = new Filter[]{
new DummyFilter() // Replaced during patching, do not touch.
};
@ -106,12 +105,15 @@ public final class LithoFilterPatch {
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
/**
* 20.22+ cannot use the thread buffer, because frequently the buffer is not correct,
* especially for components that are recreated such as dragging off screen then back on screen.
* For YouTube 20.22+, this is set to true by a patch,
* because it cannot use the thread buffer due to the buffer frequently not being correct,
* especially for components that are recreated such as dragging off-screen then back on screen.
* Instead, parse the identifier found near the start of the buffer and use that to
* identify the correct buffer to use when filtering.
* <p>
* <b>This is set during patching, do not change manually.</b>
*/
private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = VersionCheckPatch.IS_20_22_OR_GREATER;
private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = false;
/**
* Turns on additional logging, used for development purposes only.
@ -122,9 +124,11 @@ public final class LithoFilterPatch {
* String suffix for components.
* Can be any of: ".eml", ".e-b", ".eml-js", "e-js-b"
*/
private static final String LITHO_COMPONENT_EXTENSION = ".e";
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = LITHO_COMPONENT_EXTENSION.getBytes(StandardCharsets.US_ASCII);
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
/**
* Used as placeholder for litho id/path filters that do not use a buffer
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**

View file

@ -1,9 +1,12 @@
package app.revanced.patches.music.misc.litho.filter
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.shared.conversionContextToStringMethod
import app.revanced.patches.shared.misc.litho.filter.EXTENSION_CLASS_DESCRIPTOR
import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceLegacyMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
@ -13,6 +16,12 @@ val lithoFilterPatch = lithoFilterPatch(
indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
},
getConversionContextToStringMethod = BytecodePatchContext::conversionContextToStringMethod::get,
insertProtobufHook = {
protobufBufferReferenceLegacyMethod.addInstruction(
0,
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
)
},
) {
dependsOn(sharedExtensionPatch)
}

View file

@ -1,21 +1,7 @@
package app.revanced.patches.shared.misc.litho.filter
import app.revanced.patcher.accessFlags
import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.custom
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMethod
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.gettingFirstMutableMethod
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.opcodes
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
import app.revanced.patcher.strings
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@ -24,10 +10,30 @@ internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMutableMethod
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
}
internal val BytecodePatchContext.protobufBufferReferenceMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("[B")
var methodDefiningClass = ""
custom {
methodDefiningClass = definingClass
true
}
instructions(
allOf(
Opcode.IGET_OBJECT(),
field { definingClass == methodDefiningClass && type == "Lcom/google/android/libraries/elements/adl/UpbMessage;" },
),
method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniDecode" },
)
}
/**
* Matches a method that use the protobuf of our component.
*/
internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstMutableMethodDeclaratively {
internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("I", "Ljava/nio/ByteBuffer;")
@ -38,12 +44,11 @@ internal val BytecodePatchContext.componentContextParserMethodMatch by composing
instructions("Number of bits must be positive"())
}
internal val BytecodePatchContext.emptyComponentMethod by gettingFirstMethodDeclaratively("EmptyComponent") {
internal val BytecodePatchContext.emptyComponentMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
parameterTypes()
custom {
immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1
}
instructions("EmptyComponent"())
custom { immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 }
}
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMutableMethod(

View file

@ -2,9 +2,9 @@
package app.revanced.patches.shared.misc.litho.filter
import app.revanced.patcher.extensions.addInstruction
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.removeInstructions
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstClassDef
@ -13,15 +13,12 @@ import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.conversionContextToStringMethod
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.findFieldFromToString
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.immutable.value.ImmutableBooleanEncodedValue
/**
* Used to add a hook point to the extension stub.
@ -34,20 +31,24 @@ lateinit var addLithoFilter: (String) -> Unit
*/
private var filterCount = 0
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/litho/LithoFilterPatch;"
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/litho/LithoFilterPatch;"
/**
* A patch that allows to filter Litho components based on their identifier or path.
*
* @param componentCreateInsertionIndex The index to insert the filtering code in the component create method.
* @param getConversionContextToStringMethod The Method of the conversion context to string method.
* @param insertProtobufHook This method injects a setProtoBuffer call in the protobuf decoding logic.
* @param getConversionContextToStringMethod The getter of the conversion context toString method.
* @param getExtractIdentifierFromBuffer Whether to extract the identifier from the protobuf buffer.
* @param executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch.
*/
internal fun lithoFilterPatch(
componentCreateInsertionIndex: Method.() -> Int,
getConversionContextToStringMethod: BytecodePatchContext.() -> Method,
insertProtobufHook: BytecodePatchContext.() -> Unit,
executeBlock: BytecodePatchContext.() -> Unit = {},
getConversionContextToStringMethod: BytecodePatchContext.() -> Method,
getExtractIdentifierFromBuffer: () -> Boolean = { false },
block: BytecodePatchBuilder.() -> Unit = {},
) = bytecodePatch(
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
@ -111,11 +112,14 @@ internal fun lithoFilterPatch(
}
}
// Tell the extension whether to extract the identifier from the buffer.
if (getExtractIdentifierFromBuffer()) {
lithoFilterMethod.classDef.fields.first { it.name == "EXTRACT_IDENTIFIER_FROM_BUFFER" }
.initialValue = ImmutableBooleanEncodedValue.forBoolean(true).toMutable()
}
// Add an interceptor to steal the protobuf of our component.
protobufBufferReferenceMethod.addInstruction(
0,
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
)
insertProtobufHook()
// Hook the method that parses bytes into a ComponentContext.
// Allow the method to run to completion, and override the
@ -123,18 +127,11 @@ internal fun lithoFilterPatch(
// It is important to allow the original code to always run to completion,
// otherwise high memory usage and poor app performance can occur.
// Find the identifier/path fields of the conversion context.
val conversionContextIdentifierField = componentContextParserMethodMatch.let {
// Identifier field is loaded just before the string declaration.
val index = it.method.indexOfFirstInstructionReversedOrThrow(it[0]) {
// Our instruction reads a String from a field of the ConversionContext class.
val reference = getReference<FieldReference>()
reference?.definingClass == conversionContextToStringMethod.immutableClassDef.type &&
reference.type == "Ljava/lang/String;"
}
val conversionContextToStringMethod = getConversionContextToStringMethod()
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()!!
}
// Find the identifier/path fields of the conversion context.
val conversionContextIdentifierField = conversionContextToStringMethod
.findFieldFromToString("identifierProperty=")
val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
@ -162,7 +159,11 @@ internal fun lithoFilterPatch(
insertIndex,
"""
move-object/from16 v$freeRegister, p2 # ConversionContext parameter
check-cast v$freeRegister, ${getConversionContextToStringMethod().immutableClassDef.type} # Check we got the actual ConversionContext
# In YT 20.41 the field is the abstract superclass.
# Check it's the actual ConversionContext just in case.
instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef.type}
if-eqz v$identifierRegister, :unfiltered
# Get identifier and path from ConversionContext
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField

View file

@ -13,9 +13,9 @@ import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch

View file

@ -6,7 +6,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playservice.is_20_22_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch

View file

@ -9,7 +9,7 @@ import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.*
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_20_26_or_greater

View file

@ -12,8 +12,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch

View file

@ -5,7 +5,7 @@ import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen

View file

@ -16,8 +16,8 @@ import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.*

View file

@ -14,8 +14,8 @@ import app.revanced.patches.shared.misc.settings.preference.NonInteractivePrefer
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.*

View file

@ -5,66 +5,6 @@ import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMutableMethodDeclaratively {
instructions(
"Element missing correct type extension"(),
"Element missing type"(),
)
}
internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMutableMethodDeclaratively {
definingClass { endsWith("/LithoFilterPatch;") }
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
}
internal val BytecodePatchContext.protobufBufferReferenceMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("[B")
var methodDefiningClass = ""
custom {
methodDefiningClass = definingClass
true
}
instructions(
allOf(
Opcode.IGET_OBJECT(),
field { definingClass == methodDefiningClass && type == "Lcom/google/android/libraries/elements/adl/UpbMessage;" },
),
method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniDecode" },
)
}
internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("V")
parameterTypes("I", "Ljava/nio/ByteBuffer;")
opcodes(
Opcode.IPUT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.SUB_INT_2ADDR,
)
}
internal val BytecodePatchContext.emptyComponentMethod by gettingFirstMethodDeclaratively {
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
parameterTypes()
instructions("EmptyComponent"())
custom { immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 }
}
internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameterTypes("I", "I", "I")
instructions(1L()) // 1L = default thread timeout.
custom {
immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;"
}
}
internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMutableMethodDeclaratively {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("Z")

View file

@ -3,91 +3,29 @@
package app.revanced.patches.youtube.misc.litho.filter
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.removeInstructions
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstClassDef
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.youtube.shared.conversionContextToStringMethod
import app.revanced.patches.shared.misc.litho.filter.EXTENSION_CLASS_DESCRIPTOR
import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceLegacyMethod
import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceMethodMatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.*
import app.revanced.patches.youtube.shared.conversionContextToStringMethod
import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
import app.revanced.util.returnLate
import com.android.tools.smali.dexlib2.Opcode
lateinit var addLithoFilter: (String) -> Unit
private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/litho/LithoFilterPatch;"
val lithoFilterPatch = bytecodePatch(
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
) {
dependsOn(
sharedExtensionPatch,
versionCheckPatch,
)
var filterCount = 0
/**
* The following patch inserts a hook into the method that parses the bytes into a ComponentContext.
* This method contains a StringBuilder object that represents the pathBuilder of the component.
* The pathBuilder is used to filter components by their path.
*
* Additionally, the method contains a reference to the component's identifier.
* The identifier is used to filter components by their identifier.
*
* The protobuf buffer is passed along from a different injection point before the filtering occurs.
* The buffer is a large byte array that represents the component tree.
* This byte array is searched for strings that indicate the current component.
*
* All modifications done here must allow all the original code to still execute
* even when filtering, otherwise memory leaks or poor app performance may occur.
*
* The following pseudocode shows how this patch works:
*
* class SomeOtherClass {
* // Called before ComponentContextParser.parseComponent() method.
* public void someOtherMethod(ByteBuffer byteBuffer) {
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
* ...
* }
* }
*
* class ComponentContextParser {
* public Component parseComponent() {
* ...
*
* if (extensionClass.shouldFilter()) { // Inserted by this patch.
* return emptyComponent;
* }
* return originalUnpatchedComponent; // Original code.
* }
* }
*/
apply {
// Remove dummy filter from extenion static field
// and add the filters included during patching.
lithoFilterMethod.apply {
removeInstructions(2, 4) // Remove dummy filter.
addLithoFilter = { classDescriptor ->
addInstructions(
2,
"""
new-instance v1, $classDescriptor
invoke-direct { v1 }, $classDescriptor-><init>()V
const/16 v2, ${filterCount++}
aput-object v1, v0, v2
""",
)
}
val lithoFilterPatch = lithoFilterPatch(
componentCreateInsertionIndex = {
if (is_19_17_or_greater) {
indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
} else {
// 19.16 clobbers p2 so must check at start of the method and not at the return index.
0
}
// region Pass the buffer into extension.
},
insertProtobufHook = {
if (is_20_22_or_greater) {
// Hook method that bridges between UPB buffer native code and FB Litho.
// Method is found in 19.25+, but is forcefully turned off for 20.21 and lower.
@ -100,91 +38,15 @@ val lithoFilterPatch = bytecodePatch(
}
}
// Legacy Non native buffer.
// Legacy non-native buffer.
protobufBufferReferenceLegacyMethod.addInstruction(
0,
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
)
// endregion
// region Modify the create component method and
// if the component is filtered then return an empty component.
// Find the identifier/path fields of the conversion context.
val conversionContextIdentifierField = conversionContextToStringMethod
.findFieldFromToString("identifierProperty=")
val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
// Find class and methods to create an empty component.
val builderMethodDescriptor = emptyComponentMethod.immutableClassDef.methods.single {
// The only static method in the class.
method ->
AccessFlags.STATIC.isSet(method.accessFlags)
}
val emptyComponentField = firstClassDef(builderMethodDescriptor.returnType).fields.single()
componentCreateMethod.apply {
val insertIndex = if (is_19_17_or_greater) {
indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
} else {
// 19.16 clobbers p2 so must check at start of the method and not at the return index.
0
}
val freeRegister = findFreeRegister(insertIndex)
val identifierRegister = findFreeRegister(insertIndex, freeRegister)
val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister)
addInstructionsAtControlFlowLabel(
insertIndex,
"""
move-object/from16 v$freeRegister, p2
# 20.41 field is the abstract superclass.
# Verify it's the expected subclass just in case.
instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef.type}
if-eqz v$identifierRegister, :unfiltered
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
move-result v$freeRegister
if-eqz v$freeRegister, :unfiltered
# Return an empty component
move-object/from16 v$freeRegister, p1
invoke-static { v$freeRegister }, $builderMethodDescriptor
move-result-object v$freeRegister
iget-object v$freeRegister, v$freeRegister, $emptyComponentField
return-object v$freeRegister
:unfiltered
nop
""",
)
}
// endregion
// region Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube.
lithoThreadExecutorMethod.addInstructions(
0,
"""
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorCorePoolSize(I)I
move-result p1
invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorMaxThreads(I)I
move-result p2
""",
)
// endregion
},
getConversionContextToStringMethod = BytecodePatchContext::conversionContextToStringMethod::get,
getExtractIdentifierFromBuffer = { is_20_21_or_greater },
executeBlock = {
// region A/B test of new Litho native code.
// Turn off native code that handles litho component names. If this feature is on then nearly
@ -205,11 +67,8 @@ val lithoFilterPatch = bytecodePatch(
false,
)
}
// endregion
}
afterDependents {
lithoFilterMethod.replaceInstruction(0, "const/16 v0, $filterCount")
}
) {
dependsOn(sharedExtensionPatch, versionCheckPatch)
}

View file

@ -10,8 +10,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceType
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch

View file

@ -16,9 +16,9 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.interaction.seekbar.customTapAndHoldMethodMatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_34_or_greater