@@ -4,27 +4,33 @@ package app.revanced.patches.youtube.misc.litho.filter
44
55import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
66import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
7- import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
87import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
98import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
109import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
10+ import app.revanced.patcher.patch.PatchException
1111import app.revanced.patcher.patch.bytecodePatch
12- import app.revanced.patcher.util.smali.ExternalLabel
12+ import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
13+ import app.revanced.patches.youtube.layout.returnyoutubedislike.conversionContextFingerprint
1314import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
1415import app.revanced.patches.youtube.misc.playservice.is_19_18_or_greater
1516import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
1617import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
1718import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
19+ import app.revanced.util.addInstructionsAtControlFlowLabel
1820import app.revanced.util.findFreeRegister
21+ import app.revanced.util.findInstructionIndicesReversedOrThrow
1922import app.revanced.util.getReference
2023import app.revanced.util.indexOfFirstInstructionOrThrow
24+ import app.revanced.util.indexOfFirstInstructionReversed
2125import app.revanced.util.indexOfFirstInstructionReversedOrThrow
2226import com.android.tools.smali.dexlib2.AccessFlags
2327import com.android.tools.smali.dexlib2.Opcode
28+ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
2429import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
25- import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
30+ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
2631import com.android.tools.smali.dexlib2.iface.reference.FieldReference
2732import com.android.tools.smali.dexlib2.iface.reference.MethodReference
33+ import com.android.tools.smali.dexlib2.immutable.ImmutableField
2834
2935lateinit var addLithoFilter: (String ) -> Unit
3036 private set
@@ -53,42 +59,48 @@ val lithoFilterPatch = bytecodePatch(
5359 * The buffer is a large byte array that represents the component tree.
5460 * This byte array is searched for strings that indicate the current component.
5561 *
56- * The following pseudocode shows how the patch works:
62+ * All modifications done here must allow all the original code to still execute
63+ * even when filtering, otherwise memory leaks or poor app performance may occur.
64+ *
65+ * The following pseudocode shows how this patch works:
5766 *
5867 * class SomeOtherClass {
59- * // Called before ComponentContextParser.parseBytesToComponentContext method.
68+ * // Called before ComponentContextParser.readComponentIdentifier(...) method.
6069 * public void someOtherMethod(ByteBuffer byteBuffer) {
6170 * ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
6271 * ...
6372 * }
6473 * }
6574 *
66- * When patching 19.17 and earlier :
75+ * When patching 19.16 :
6776 *
6877 * class ComponentContextParser {
69- * public ComponentContext ReadComponentIdentifierFingerprint (...) {
78+ * public Component readComponentIdentifier (...) {
7079 * ...
71- * if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
80+ * if (extensionClass.filter(identifier, pathBuilder)) { // Inserted by this patch.
7281 * return emptyComponent;
73- * ...
82+ * }
83+ * return originalUnpatchedComponent;
7484 * }
7585 * }
7686 *
7787 * When patching 19.18 and later:
7888 *
7989 * class ComponentContextParser {
80- * public ComponentContext parseBytesToComponentContext (...) {
90+ * public ComponentIdentifierObj readComponentIdentifier (...) {
8191 * ...
82- * if (ReadComponentIdentifierFingerprint() == null); // Inserted by this patch.
83- * return emptyComponent;
92+ * if (extensionClass.filter(identifier, pathBuilder)) { // Inserted by this patch.
93+ * this.patch_isFiltered = true;
94+ * }
8495 * ...
8596 * }
8697 *
87- * public ComponentIdentifierObj readComponentIdentifier(...) {
88- * ...
89- * if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
90- * return null;
98+ * public Component parseBytesToComponentContext(...) {
9199 * ...
100+ * if (this.patch_isFiltered) { // Inserted by this patch.
101+ * return emptyComponent;
102+ * }
103+ * return originalUnpatchedComponent;
92104 * }
93105 * }
94106 */
@@ -115,14 +127,13 @@ val lithoFilterPatch = bytecodePatch(
115127
116128 protobufBufferReferenceFingerprint.method.addInstruction(
117129 0 ,
118- " invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR ->setProtoBuffer(Ljava/nio/ByteBuffer;)V" ,
130+ " invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR ->setProtoBuffer(Ljava/nio/ByteBuffer;)V" ,
119131 )
120132
121133 // endregion
122134
123135 // region Hook the method that parses bytes into a ComponentContext.
124136
125- val readComponentMethod = readComponentIdentifierFingerprint.originalMethod
126137 // Get the only static method in the class.
127138 val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.first { method ->
128139 AccessFlags .STATIC .isSet(method.accessFlags)
@@ -132,44 +143,47 @@ val lithoFilterPatch = bytecodePatch(
132143 builderMethodDescriptor.returnType == classDef.type
133144 }!! .immutableClass.fields.single()
134145
146+ // Add a field to store the result of the filtering. This allows checking the field
147+ // just before returning so the original code always runs the same when filtering occurs.
148+ val lithoFilterResultField = ImmutableField (
149+ componentContextParserFingerprint.classDef.type,
150+ " patch_isFiltered" ,
151+ " Z" ,
152+ AccessFlags .PRIVATE .value,
153+ null ,
154+ null ,
155+ null ,
156+ ).toMutable()
157+ componentContextParserFingerprint.classDef.fields.add(lithoFilterResultField)
158+
135159 // Returns an empty component instead of the original component.
136- fun createReturnEmptyComponentInstructions (register : Int ): String =
137- """
138- move-object/from16 v$register , p1
139- invoke-static { v$register }, $builderMethodDescriptor
140- move-result-object v$register
141- iget-object v$register , v$register , $emptyComponentField
142- return-object v$register
143- """
160+ fun returnEmptyComponentInstructions (free : Int ): String = """
161+ move-object/from16 v$free , p0
162+ iget-boolean v$free , v$free , $lithoFilterResultField
163+ if-eqz v$free , :unfiltered
164+
165+ move-object/from16 v$free , p1
166+ invoke-static { v$free }, $builderMethodDescriptor
167+ move-result-object v$free
168+ iget-object v$free , v$free , $emptyComponentField
169+ return-object v$free
170+
171+ :unfiltered
172+ nop
173+ """
144174
145175 componentContextParserFingerprint.method.apply {
146176 // 19.18 and later require patching 2 methods instead of one.
147177 // Otherwise the modifications done here are the same for all targets.
148178 if (is_19_18_or_greater) {
149- // Get the method name of the ReadComponentIdentifierFingerprint call.
150- val readComponentMethodCallIndex = indexOfFirstInstructionOrThrow {
151- val reference = getReference<MethodReference >()
152- reference?.definingClass == readComponentMethod.definingClass &&
153- reference.name == readComponentMethod.name
154- }
155-
156- // Result of read component, and also a free register.
157- val register = getInstruction<OneRegisterInstruction >(readComponentMethodCallIndex + 1 ).registerA
179+ findInstructionIndicesReversedOrThrow(Opcode .RETURN_OBJECT ).forEach { index ->
180+ val free = findFreeRegister(index)
158181
159- // Insert after 'move-result-object'
160- val insertHookIndex = readComponentMethodCallIndex + 2
161-
162- // Return an EmptyComponent instead of the original component if the filterState method returns true.
163- addInstructionsWithLabels(
164- insertHookIndex,
165- """
166- if-nez v$register , :unfiltered
167-
168- # Component was filtered in ReadComponentIdentifierFingerprint hook
169- ${createReturnEmptyComponentInstructions(register)}
170- """ ,
171- ExternalLabel (" unfiltered" , getInstruction(insertHookIndex)),
172- )
182+ addInstructionsAtControlFlowLabel(
183+ index,
184+ returnEmptyComponentInstructions(free)
185+ )
186+ }
173187 }
174188 }
175189
@@ -178,47 +192,79 @@ val lithoFilterPatch = bytecodePatch(
178192 // region Read component then store the result.
179193
180194 readComponentIdentifierFingerprint.method.apply {
181- val insertHookIndex = indexOfFirstInstructionOrThrow {
182- opcode == Opcode .IPUT_OBJECT &&
183- getReference<FieldReference >()?.type == " Ljava/lang/StringBuilder;"
195+ val returnIndex = indexOfFirstInstructionReversedOrThrow(Opcode .RETURN_OBJECT )
196+ if (indexOfFirstInstructionReversed(returnIndex - 1 , Opcode .RETURN_OBJECT ) >= 0 ) {
197+ throw PatchException (" Found multiple return indexes" ) // Patch needs an update.
198+ }
199+
200+ val elementConfigClass = elementConfigFingerprint.originalClassDef
201+ val elementConfigClassType = elementConfigClass.type
202+ val elementConfigIndex = indexOfFirstInstructionReversedOrThrow(returnIndex) {
203+ val reference = getReference<MethodReference >()
204+ reference?.definingClass == elementConfigClassType
205+ }
206+ val elementConfigStringBuilderField = elementConfigClass.fields.single { field ->
207+ field.type == " Ljava/lang/StringBuilder;"
184208 }
185- val stringBuilderRegister = getInstruction<TwoRegisterInstruction >(insertHookIndex).registerA
186209
187210 // Identifier is saved to a field just before the string builder.
188- val identifierRegister = getInstruction<TwoRegisterInstruction >(
189- indexOfFirstInstructionReversedOrThrow(insertHookIndex) {
211+ val putStringBuilderIndex = indexOfFirstInstructionOrThrow {
212+ val reference = getReference<FieldReference >()
213+ opcode == Opcode .IPUT_OBJECT &&
214+ reference?.definingClass == elementConfigClassType &&
215+ reference.type == " Ljava/lang/StringBuilder;"
216+ }
217+ val elementConfigIdentifierField = getInstruction<ReferenceInstruction >(
218+ indexOfFirstInstructionReversedOrThrow(putStringBuilderIndex) {
219+ val reference = getReference<FieldReference >()
190220 opcode == Opcode .IPUT_OBJECT &&
191- getReference<FieldReference >()?.type == " Ljava/lang/String;"
192- },
193- ).registerA
221+ reference?.definingClass == elementConfigClassType &&
222+ reference.type == " Ljava/lang/String;"
223+ }
224+ ).getReference<FieldReference >()
225+
226+ // Could use some of these free registers multiple times, but this is inserting at a
227+ // return instruction so there is always multiple 4-bit registers available.
228+ val elementConfigRegister = getInstruction<FiveRegisterInstruction >(elementConfigIndex).registerC
229+ val identifierRegister = findFreeRegister(returnIndex, elementConfigRegister)
230+ val stringBuilderRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister)
231+ val thisRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister, stringBuilderRegister)
232+ val freeRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister, stringBuilderRegister, thisRegister)
194233
195- val freeRegister = findFreeRegister(insertHookIndex, identifierRegister, stringBuilderRegister)
196234 val invokeFilterInstructions = """
235+ iget-object v$identifierRegister , v$elementConfigRegister , $elementConfigIdentifierField
236+ iget-object v$stringBuilderRegister , v$elementConfigRegister , $elementConfigStringBuilderField
197237 invoke-static { v$identifierRegister , v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR ->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
198238 move-result v$freeRegister
199- if-eqz v$freeRegister , :unfiltered
239+ move-object/from16 v$thisRegister , p0
240+ iput-boolean v$freeRegister , v$thisRegister , $lithoFilterResultField
200241 """
201242
202- addInstructionsWithLabels(
203- insertHookIndex,
204- if (is_19_18_or_greater) {
243+ if (is_19_18_or_greater) {
244+ addInstructionsAtControlFlowLabel(
245+ returnIndex,
246+ invokeFilterInstructions
247+ )
248+ } else {
249+ val elementConfigMethod = conversionContextFingerprint.originalClassDef.methods
250+ .single { method ->
251+ ! AccessFlags .STATIC .isSet(method.accessFlags) && method.returnType == elementConfigClassType
252+ }
253+
254+ addInstructionsAtControlFlowLabel(
255+ returnIndex,
205256 """
206- $invokeFilterInstructions
257+ # Element config is a method on a parameter.
258+ move-object/from16 v$elementConfigRegister , p2
259+ invoke-virtual { v$elementConfigRegister }, $elementConfigMethod
260+ move-result-object v$elementConfigRegister
207261
208- # Return null, and the ComponentContextParserFingerprint hook
209- # handles returning an empty component.
210- const/4 v$freeRegister , 0x0
211- return-object v$freeRegister
212- """
213- } else {
214- """
215262 $invokeFilterInstructions
216-
217- ${createReturnEmptyComponentInstructions (freeRegister)}
263+
264+ ${returnEmptyComponentInstructions (freeRegister)}
218265 """
219- },
220- ExternalLabel (" unfiltered" , getInstruction(insertHookIndex)),
221- )
266+ )
267+ }
222268 }
223269
224270 // endregion
0 commit comments