mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-11 11:53:55 +01:00
Compare commits
4 Commits
v5.23.0-de
...
v5.23.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ca2bb7692 | ||
|
|
23fd720fa7 | ||
|
|
1f08586ae8 | ||
|
|
60fdf4c44c |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a))
|
||||||
|
|
||||||
|
# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
|
||||||
|
|
||||||
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)
|
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ public final class LithoFilterPatch {
|
|||||||
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
||||||
*/
|
*/
|
||||||
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
|
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
|
||||||
|
/**
|
||||||
|
* Results of calling {@link #filter(String, StringBuilder)}.
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Boolean> filterResult = new ThreadLocal<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
@@ -140,11 +144,22 @@ public final class LithoFilterPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean shouldFilter() {
|
||||||
|
Boolean shouldFilter = filterResult.get();
|
||||||
|
return shouldFilter != null && shouldFilter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread, and commonly called by multiple threads at the same time.
|
* Injection point. Called off the main thread, and commonly called by multiple threads at the same time.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
public static void filter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||||
public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) {
|
filterResult.set(handleFiltering(lithoIdentifier, pathBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean handleFiltering(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||||
try {
|
try {
|
||||||
if (pathBuilder.length() == 0) {
|
if (pathBuilder.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.23.0-dev.1
|
version = 5.23.0-dev.3
|
||||||
|
|||||||
@@ -1534,6 +1534,7 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat
|
|||||||
|
|
||||||
public final class app/revanced/util/BytecodeUtilsKt {
|
public final class app/revanced/util/BytecodeUtilsKt {
|
||||||
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/util/smali/ExternalLabel;)V
|
||||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
|
||||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
|
||||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
|
||||||
|
|||||||
@@ -5,18 +5,6 @@ import app.revanced.util.literal
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
internal val conversionContextFingerprint = fingerprint {
|
|
||||||
returns("Ljava/lang/String;")
|
|
||||||
parameters()
|
|
||||||
strings(
|
|
||||||
", widthConstraint=",
|
|
||||||
", heightConstraint=",
|
|
||||||
", templateLoggerFactory=",
|
|
||||||
", rootDisposableContainer=",
|
|
||||||
"ConversionContext{containerInternal=",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val dislikeFingerprint = fingerprint {
|
internal val dislikeFingerprint = fingerprint {
|
||||||
returns("V")
|
returns("V")
|
||||||
strings("like/dislike")
|
strings("like/dislike")
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
|||||||
import app.revanced.patches.youtube.misc.settings.addSettingPreference
|
import app.revanced.patches.youtube.misc.settings.addSettingPreference
|
||||||
import app.revanced.patches.youtube.misc.settings.newIntent
|
import app.revanced.patches.youtube.misc.settings.newIntent
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
|
||||||
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateFingerprint
|
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateFingerprint
|
||||||
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
||||||
import app.revanced.patches.youtube.video.videoid.hookVideoId
|
import app.revanced.patches.youtube.video.videoid.hookVideoId
|
||||||
@@ -113,11 +114,11 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
|||||||
// This hook handles all situations, as it's where the created Spans are stored and later reused.
|
// This hook handles all situations, as it's where the created Spans are stored and later reused.
|
||||||
// Find the field name of the conversion context.
|
// Find the field name of the conversion context.
|
||||||
val conversionContextField = textComponentConstructorFingerprint.originalClassDef.fields.find {
|
val conversionContextField = textComponentConstructorFingerprint.originalClassDef.fields.find {
|
||||||
it.type == conversionContextFingerprint.originalClassDef.type
|
it.type == conversionContextFingerprintToString.originalClassDef.type
|
||||||
} ?: throw PatchException("Could not find conversion context field")
|
} ?: throw PatchException("Could not find conversion context field")
|
||||||
|
|
||||||
textComponentLookupFingerprint.match(textComponentConstructorFingerprint.originalClassDef)
|
textComponentLookupFingerprint.match(textComponentConstructorFingerprint.originalClassDef)
|
||||||
textComponentLookupFingerprint.method.apply {
|
.method.apply {
|
||||||
// Find the instruction for creating the text data object.
|
// Find the instruction for creating the text data object.
|
||||||
val textDataClassType = textComponentDataFingerprint.originalClassDef.type
|
val textDataClassType = textComponentDataFingerprint.originalClassDef.type
|
||||||
|
|
||||||
@@ -160,12 +161,12 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
|||||||
addInstructionsAtControlFlowLabel(
|
addInstructionsAtControlFlowLabel(
|
||||||
insertIndex,
|
insertIndex,
|
||||||
"""
|
"""
|
||||||
# Copy conversion context
|
# Copy conversion context
|
||||||
move-object/from16 v$tempRegister, p0
|
move-object/from16 v$tempRegister, p0
|
||||||
iget-object v$tempRegister, v$tempRegister, $conversionContextField
|
iget-object v$tempRegister, v$tempRegister, $conversionContextField
|
||||||
invoke-static { v$tempRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
invoke-static { v$tempRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
||||||
move-result-object v$charSequenceRegister
|
move-result-object v$charSequenceRegister
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,11 +202,9 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
|||||||
val charSequenceFieldReference =
|
val charSequenceFieldReference =
|
||||||
getInstruction<ReferenceInstruction>(dislikesIndex).reference
|
getInstruction<ReferenceInstruction>(dislikesIndex).reference
|
||||||
|
|
||||||
val registerCount = implementation!!.registerCount
|
val conversionContextRegister = implementation!!.registerCount - parameters.size + 1
|
||||||
|
|
||||||
// This register is being overwritten, so it is free to use.
|
val freeRegister = findFreeRegister(insertIndex, charSequenceInstanceRegister, conversionContextRegister)
|
||||||
val freeRegister = registerCount - 1
|
|
||||||
val conversionContextRegister = registerCount - parameters.size + 1
|
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
insertIndex,
|
insertIndex,
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ import app.revanced.util.literal
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
/**
|
|
||||||
* In 19.17 and earlier, this resolves to the same method as [readComponentIdentifierFingerprint].
|
|
||||||
* In 19.18+ this resolves to a different method.
|
|
||||||
*/
|
|
||||||
internal val componentContextParserFingerprint = fingerprint {
|
internal val componentContextParserFingerprint = fingerprint {
|
||||||
strings(
|
strings(
|
||||||
"TreeNode result must be set.",
|
"TreeNode result must be set.",
|
||||||
@@ -17,11 +13,21 @@ internal val componentContextParserFingerprint = fingerprint {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves to the class found in [componentContextParserFingerprint].
|
||||||
|
* When patching 19.16 this fingerprint matches the same method as [componentContextParserFingerprint].
|
||||||
|
*/
|
||||||
|
internal val componentContextSubParserFingerprint = fingerprint {
|
||||||
|
strings(
|
||||||
|
"Number of bits must be positive"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal val lithoFilterFingerprint = fingerprint {
|
internal val lithoFilterFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||||
returns("V")
|
returns("V")
|
||||||
custom { _, classDef ->
|
custom { _, classDef ->
|
||||||
classDef.endsWith("LithoFilterPatch;")
|
classDef.endsWith("/LithoFilterPatch;")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +43,6 @@ internal val protobufBufferReferenceFingerprint = fingerprint {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In 19.17 and earlier, this resolves to the same method as [componentContextParserFingerprint].
|
|
||||||
* In 19.18+ this resolves to a different method.
|
|
||||||
*/
|
|
||||||
internal val readComponentIdentifierFingerprint = fingerprint {
|
|
||||||
strings("Number of bits must be positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val emptyComponentFingerprint = fingerprint {
|
internal val emptyComponentFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
||||||
parameters()
|
parameters()
|
||||||
|
|||||||
@@ -4,25 +4,25 @@ package app.revanced.patches.youtube.misc.litho.filter
|
|||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
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.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_18_or_greater
|
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
|
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
|
||||||
|
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||||
import app.revanced.util.findFreeRegister
|
import app.revanced.util.findFreeRegister
|
||||||
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
@@ -53,42 +53,33 @@ val lithoFilterPatch = bytecodePatch(
|
|||||||
* The buffer is a large byte array that represents the component tree.
|
* The buffer is a large byte array that represents the component tree.
|
||||||
* This byte array is searched for strings that indicate the current component.
|
* This byte array is searched for strings that indicate the current component.
|
||||||
*
|
*
|
||||||
* The following pseudocode shows how the patch works:
|
* 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 {
|
* class SomeOtherClass {
|
||||||
* // Called before ComponentContextParser.parseBytesToComponentContext method.
|
* // Called before ComponentContextParser.parseComponent() method.
|
||||||
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
||||||
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* When patching 19.17 and earlier:
|
|
||||||
*
|
|
||||||
* class ComponentContextParser {
|
* class ComponentContextParser {
|
||||||
* public ComponentContext ReadComponentIdentifierFingerprint(...) {
|
* public Component parseComponent() {
|
||||||
* ...
|
* ...
|
||||||
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
|
*
|
||||||
|
* // Checks if the component should be filtered.
|
||||||
|
* // Sets a thread local with the filtering result.
|
||||||
|
* extensionClass.filter(identifier, pathBuilder); // Inserted by this patch.
|
||||||
|
*
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* if (extensionClass.shouldFilter()) { // Inserted by this patch.
|
||||||
* return emptyComponent;
|
* return emptyComponent;
|
||||||
* ...
|
* }
|
||||||
* }
|
* return originalUnpatchedComponent; // Original code.
|
||||||
* }
|
|
||||||
*
|
|
||||||
* When patching 19.18 and later:
|
|
||||||
*
|
|
||||||
* class ComponentContextParser {
|
|
||||||
* public ComponentContext parseBytesToComponentContext(...) {
|
|
||||||
* ...
|
|
||||||
* if (ReadComponentIdentifierFingerprint() == null); // Inserted by this patch.
|
|
||||||
* return emptyComponent;
|
|
||||||
* ...
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public ComponentIdentifierObj readComponentIdentifier(...) {
|
|
||||||
* ...
|
|
||||||
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
|
|
||||||
* return null;
|
|
||||||
* ...
|
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
@@ -103,7 +94,7 @@ val lithoFilterPatch = bytecodePatch(
|
|||||||
2,
|
2,
|
||||||
"""
|
"""
|
||||||
new-instance v1, $classDescriptor
|
new-instance v1, $classDescriptor
|
||||||
invoke-direct {v1}, $classDescriptor-><init>()V
|
invoke-direct { v1 }, $classDescriptor-><init>()V
|
||||||
const/16 v2, ${filterCount++}
|
const/16 v2, ${filterCount++}
|
||||||
aput-object v1, v0, v2
|
aput-object v1, v0, v2
|
||||||
""",
|
""",
|
||||||
@@ -115,110 +106,105 @@ val lithoFilterPatch = bytecodePatch(
|
|||||||
|
|
||||||
protobufBufferReferenceFingerprint.method.addInstruction(
|
protobufBufferReferenceFingerprint.method.addInstruction(
|
||||||
0,
|
0,
|
||||||
" invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Hook the method that parses bytes into a ComponentContext.
|
// region Hook the method that parses bytes into a ComponentContext.
|
||||||
|
|
||||||
val readComponentMethod = readComponentIdentifierFingerprint.originalMethod
|
// Allow the method to run to completion, and override the
|
||||||
// Get the only static method in the class.
|
// return value with an empty component if it should be filtered.
|
||||||
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.first { method ->
|
// It is important to allow the original code to always run to completion,
|
||||||
AccessFlags.STATIC.isSet(method.accessFlags)
|
// otherwise memory leaks and poor app performance can occur.
|
||||||
}
|
//
|
||||||
// Only one field.
|
// The extension filtering result needs to be saved off somewhere, but cannot
|
||||||
val emptyComponentField = classBy { classDef ->
|
// save to a class field since the target class is called by multiple threads.
|
||||||
builderMethodDescriptor.returnType == classDef.type
|
// It would be great if there was a way to change the register count of the
|
||||||
}!!.immutableClass.fields.single()
|
// method implementation and save the result to a high register to later use
|
||||||
|
// in the method, but there is no simple way to do that.
|
||||||
// Returns an empty component instead of the original component.
|
// Instead save the extension filter result to a thread local and check the
|
||||||
fun createReturnEmptyComponentInstructions(register: Int): String =
|
// filtering result at each method return index.
|
||||||
"""
|
// String field for the litho identifier.
|
||||||
move-object/from16 v$register, p1
|
|
||||||
invoke-static { v$register }, $builderMethodDescriptor
|
|
||||||
move-result-object v$register
|
|
||||||
iget-object v$register, v$register, $emptyComponentField
|
|
||||||
return-object v$register
|
|
||||||
"""
|
|
||||||
|
|
||||||
componentContextParserFingerprint.method.apply {
|
componentContextParserFingerprint.method.apply {
|
||||||
// 19.18 and later require patching 2 methods instead of one.
|
val conversionContextClass = conversionContextFingerprintToString.originalClassDef
|
||||||
// Otherwise the modifications done here are the same for all targets.
|
|
||||||
if (is_19_18_or_greater) {
|
val conversionContextIdentifierField = componentContextSubParserFingerprint.match(
|
||||||
// Get the method name of the ReadComponentIdentifierFingerprint call.
|
componentContextParserFingerprint.originalClassDef
|
||||||
val readComponentMethodCallIndex = indexOfFirstInstructionOrThrow {
|
).let {
|
||||||
val reference = getReference<MethodReference>()
|
// Identifier field is loaded just before the string declaration.
|
||||||
reference?.definingClass == readComponentMethod.definingClass &&
|
val index = it.method.indexOfFirstInstructionReversedOrThrow(
|
||||||
reference.name == readComponentMethod.name
|
it.stringMatches!!.first().index
|
||||||
|
) {
|
||||||
|
val reference = getReference<FieldReference>()
|
||||||
|
reference?.definingClass == conversionContextClass.type
|
||||||
|
&& reference.type == "Ljava/lang/String;"
|
||||||
}
|
}
|
||||||
|
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()
|
||||||
// Result of read component, and also a free register.
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(readComponentMethodCallIndex + 1).registerA
|
|
||||||
|
|
||||||
// Insert after 'move-result-object'
|
|
||||||
val insertHookIndex = readComponentMethodCallIndex + 2
|
|
||||||
|
|
||||||
// Return an EmptyComponent instead of the original component if the filterState method returns true.
|
|
||||||
addInstructionsWithLabels(
|
|
||||||
insertHookIndex,
|
|
||||||
"""
|
|
||||||
if-nez v$register, :unfiltered
|
|
||||||
|
|
||||||
# Component was filtered in ReadComponentIdentifierFingerprint hook
|
|
||||||
${createReturnEmptyComponentInstructions(register)}
|
|
||||||
""",
|
|
||||||
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
// StringBuilder field for the litho path.
|
||||||
|
val conversionContextPathBuilderField = conversionContextClass.fields
|
||||||
|
.single { field -> field.type == "Ljava/lang/StringBuilder;" }
|
||||||
|
|
||||||
// region Read component then store the result.
|
val conversionContextResultIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.returnType == conversionContextClass.type
|
||||||
|
} + 1
|
||||||
|
|
||||||
readComponentIdentifierFingerprint.method.apply {
|
val conversionContextResultRegister = getInstruction<OneRegisterInstruction>(
|
||||||
val insertHookIndex = indexOfFirstInstructionOrThrow {
|
conversionContextResultIndex
|
||||||
opcode == Opcode.IPUT_OBJECT &&
|
|
||||||
getReference<FieldReference>()?.type == "Ljava/lang/StringBuilder;"
|
|
||||||
}
|
|
||||||
val stringBuilderRegister = getInstruction<TwoRegisterInstruction>(insertHookIndex).registerA
|
|
||||||
|
|
||||||
// Identifier is saved to a field just before the string builder.
|
|
||||||
val identifierRegister = getInstruction<TwoRegisterInstruction>(
|
|
||||||
indexOfFirstInstructionReversedOrThrow(insertHookIndex) {
|
|
||||||
opcode == Opcode.IPUT_OBJECT &&
|
|
||||||
getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
|
||||||
},
|
|
||||||
).registerA
|
).registerA
|
||||||
|
|
||||||
val freeRegister = findFreeRegister(insertHookIndex, identifierRegister, stringBuilderRegister)
|
val identifierRegister = findFreeRegister(
|
||||||
val invokeFilterInstructions = """
|
conversionContextResultIndex, conversionContextResultRegister
|
||||||
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
|
||||||
move-result v$freeRegister
|
|
||||||
if-eqz v$freeRegister, :unfiltered
|
|
||||||
"""
|
|
||||||
|
|
||||||
addInstructionsWithLabels(
|
|
||||||
insertHookIndex,
|
|
||||||
if (is_19_18_or_greater) {
|
|
||||||
"""
|
|
||||||
$invokeFilterInstructions
|
|
||||||
|
|
||||||
# Return null, and the ComponentContextParserFingerprint hook
|
|
||||||
# handles returning an empty component.
|
|
||||||
const/4 v$freeRegister, 0x0
|
|
||||||
return-object v$freeRegister
|
|
||||||
"""
|
|
||||||
} else {
|
|
||||||
"""
|
|
||||||
$invokeFilterInstructions
|
|
||||||
|
|
||||||
${createReturnEmptyComponentInstructions(freeRegister)}
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
|
|
||||||
)
|
)
|
||||||
|
val stringBuilderRegister = findFreeRegister(
|
||||||
|
conversionContextResultIndex, conversionContextResultRegister, identifierRegister
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the component should be filtered, and save the result to a thread local.
|
||||||
|
addInstructionsAtControlFlowLabel(
|
||||||
|
conversionContextResultIndex + 1,
|
||||||
|
"""
|
||||||
|
iget-object v$identifierRegister, v$conversionContextResultRegister, $conversionContextIdentifierField
|
||||||
|
iget-object v$stringBuilderRegister, v$conversionContextResultRegister, $conversionContextPathBuilderField
|
||||||
|
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)V
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the only static method in the class.
|
||||||
|
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single {
|
||||||
|
method -> AccessFlags.STATIC.isSet(method.accessFlags)
|
||||||
|
}
|
||||||
|
// Only one field.
|
||||||
|
val emptyComponentField = classBy { classDef ->
|
||||||
|
classDef.type == builderMethodDescriptor.returnType
|
||||||
|
}!!.immutableClass.fields.single()
|
||||||
|
|
||||||
|
// Check at each return value if the component is filtered,
|
||||||
|
// and return an empty component if filtering is needed.
|
||||||
|
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { returnIndex ->
|
||||||
|
val freeRegister = findFreeRegister(returnIndex)
|
||||||
|
|
||||||
|
addInstructionsAtControlFlowLabel(
|
||||||
|
returnIndex,
|
||||||
|
"""
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->shouldFilter()Z
|
||||||
|
move-result v$freeRegister
|
||||||
|
if-eqz v$freeRegister, :unfiltered
|
||||||
|
|
||||||
|
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
|
// endregion
|
||||||
|
|||||||
@@ -4,6 +4,21 @@ import app.revanced.patcher.fingerprint
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val conversionContextFingerprintToString = fingerprint {
|
||||||
|
parameters()
|
||||||
|
strings(
|
||||||
|
"ConversionContext{containerInternal=",
|
||||||
|
", widthConstraint=",
|
||||||
|
", heightConstraint=",
|
||||||
|
", templateLoggerFactory=",
|
||||||
|
", rootDisposableContainer=",
|
||||||
|
", identifierProperty="
|
||||||
|
)
|
||||||
|
custom { method, _ ->
|
||||||
|
method.name == "toString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal val autoRepeatFingerprint = fingerprint {
|
internal val autoRepeatFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returns("V")
|
returns("V")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
|||||||
import app.revanced.patcher.patch.PatchException
|
import app.revanced.patcher.patch.PatchException
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
import app.revanced.patches.shared.misc.mapping.get
|
import app.revanced.patches.shared.misc.mapping.get
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
@@ -207,6 +208,26 @@ fun MutableMethod.injectHideViewCall(
|
|||||||
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
|
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts instructions at a given index, using the existing control flow label at that index.
|
||||||
|
* Inserted instructions can have it's own control flow labels as well.
|
||||||
|
*
|
||||||
|
* Effectively this changes the code from:
|
||||||
|
* :label
|
||||||
|
* (original code)
|
||||||
|
*
|
||||||
|
* Into:
|
||||||
|
* :label
|
||||||
|
* (patch code)
|
||||||
|
* (original code)
|
||||||
|
*/
|
||||||
|
// TODO: delete this on next major version bump.
|
||||||
|
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
||||||
|
insertIndex: Int,
|
||||||
|
instructions: String
|
||||||
|
) = addInstructionsAtControlFlowLabel(insertIndex, instructions, *arrayOf<ExternalLabel>())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts instructions at a given index, using the existing control flow label at that index.
|
* Inserts instructions at a given index, using the existing control flow label at that index.
|
||||||
* Inserted instructions can have it's own control flow labels as well.
|
* Inserted instructions can have it's own control flow labels as well.
|
||||||
@@ -223,13 +244,14 @@ fun MutableMethod.injectHideViewCall(
|
|||||||
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
||||||
insertIndex: Int,
|
insertIndex: Int,
|
||||||
instructions: String,
|
instructions: String,
|
||||||
|
vararg externalLabels: ExternalLabel
|
||||||
) {
|
) {
|
||||||
// Duplicate original instruction and add to +1 index.
|
// Duplicate original instruction and add to +1 index.
|
||||||
addInstruction(insertIndex + 1, getInstruction(insertIndex))
|
addInstruction(insertIndex + 1, getInstruction(insertIndex))
|
||||||
|
|
||||||
// Add patch code at same index as duplicated instruction,
|
// Add patch code at same index as duplicated instruction,
|
||||||
// so it uses the original instruction control flow label.
|
// so it uses the original instruction control flow label.
|
||||||
addInstructionsWithLabels(insertIndex + 1, instructions)
|
addInstructionsWithLabels(insertIndex + 1, instructions, *externalLabels)
|
||||||
|
|
||||||
// Remove original non duplicated instruction.
|
// Remove original non duplicated instruction.
|
||||||
removeInstruction(insertIndex)
|
removeInstruction(insertIndex)
|
||||||
|
|||||||
Reference in New Issue
Block a user