@@ -4,27 +4,33 @@ package app.revanced.patches.youtube.misc.litho.filter
4
4
5
5
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
6
6
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
7
- import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
8
7
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
9
8
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
10
9
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
10
+ import app.revanced.patcher.patch.PatchException
11
11
import 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
13
14
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
14
15
import app.revanced.patches.youtube.misc.playservice.is_19_18_or_greater
15
16
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
16
17
import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
17
18
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
19
+ import app.revanced.util.addInstructionsAtControlFlowLabel
18
20
import app.revanced.util.findFreeRegister
21
+ import app.revanced.util.findInstructionIndicesReversedOrThrow
19
22
import app.revanced.util.getReference
20
23
import app.revanced.util.indexOfFirstInstructionOrThrow
24
+ import app.revanced.util.indexOfFirstInstructionReversed
21
25
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
22
26
import com.android.tools.smali.dexlib2.AccessFlags
23
27
import com.android.tools.smali.dexlib2.Opcode
28
+ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
24
29
import 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
26
31
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
27
32
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
33
+ import com.android.tools.smali.dexlib2.immutable.ImmutableField
28
34
29
35
lateinit var addLithoFilter: (String ) -> Unit
30
36
private set
@@ -53,42 +59,48 @@ val lithoFilterPatch = bytecodePatch(
53
59
* The buffer is a large byte array that represents the component tree.
54
60
* This byte array is searched for strings that indicate the current component.
55
61
*
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:
57
66
*
58
67
* class SomeOtherClass {
59
- * // Called before ComponentContextParser.parseBytesToComponentContext method.
68
+ * // Called before ComponentContextParser.readComponentIdentifier(...) method.
60
69
* public void someOtherMethod(ByteBuffer byteBuffer) {
61
70
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
62
71
* ...
63
72
* }
64
73
* }
65
74
*
66
- * When patching 19.17 and earlier :
75
+ * When patching 19.16 :
67
76
*
68
77
* class ComponentContextParser {
69
- * public ComponentContext ReadComponentIdentifierFingerprint (...) {
78
+ * public Component readComponentIdentifier (...) {
70
79
* ...
71
- * if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
80
+ * if (extensionClass.filter(identifier, pathBuilder)) { // Inserted by this patch.
72
81
* return emptyComponent;
73
- * ...
82
+ * }
83
+ * return originalUnpatchedComponent;
74
84
* }
75
85
* }
76
86
*
77
87
* When patching 19.18 and later:
78
88
*
79
89
* class ComponentContextParser {
80
- * public ComponentContext parseBytesToComponentContext (...) {
90
+ * public ComponentIdentifierObj readComponentIdentifier (...) {
81
91
* ...
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
+ * }
84
95
* ...
85
96
* }
86
97
*
87
- * public ComponentIdentifierObj readComponentIdentifier(...) {
88
- * ...
89
- * if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
90
- * return null;
98
+ * public Component parseBytesToComponentContext(...) {
91
99
* ...
100
+ * if (this.patch_isFiltered) { // Inserted by this patch.
101
+ * return emptyComponent;
102
+ * }
103
+ * return originalUnpatchedComponent;
92
104
* }
93
105
* }
94
106
*/
@@ -115,14 +127,13 @@ val lithoFilterPatch = bytecodePatch(
115
127
116
128
protobufBufferReferenceFingerprint.method.addInstruction(
117
129
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" ,
119
131
)
120
132
121
133
// endregion
122
134
123
135
// region Hook the method that parses bytes into a ComponentContext.
124
136
125
- val readComponentMethod = readComponentIdentifierFingerprint.originalMethod
126
137
// Get the only static method in the class.
127
138
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.first { method ->
128
139
AccessFlags .STATIC .isSet(method.accessFlags)
@@ -132,44 +143,47 @@ val lithoFilterPatch = bytecodePatch(
132
143
builderMethodDescriptor.returnType == classDef.type
133
144
}!! .immutableClass.fields.single()
134
145
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
+
135
159
// 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
+ """
144
174
145
175
componentContextParserFingerprint.method.apply {
146
176
// 19.18 and later require patching 2 methods instead of one.
147
177
// Otherwise the modifications done here are the same for all targets.
148
178
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)
158
181
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
+ }
173
187
}
174
188
}
175
189
@@ -178,47 +192,79 @@ val lithoFilterPatch = bytecodePatch(
178
192
// region Read component then store the result.
179
193
180
194
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;"
184
208
}
185
- val stringBuilderRegister = getInstruction<TwoRegisterInstruction >(insertHookIndex).registerA
186
209
187
210
// 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 >()
190
220
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)
194
233
195
- val freeRegister = findFreeRegister(insertHookIndex, identifierRegister, stringBuilderRegister)
196
234
val invokeFilterInstructions = """
235
+ iget-object v$identifierRegister , v$elementConfigRegister , $elementConfigIdentifierField
236
+ iget-object v$stringBuilderRegister , v$elementConfigRegister , $elementConfigStringBuilderField
197
237
invoke-static { v$identifierRegister , v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR ->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
198
238
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
200
241
"""
201
242
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,
205
256
"""
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
207
261
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
- """
215
262
$invokeFilterInstructions
216
-
217
- ${createReturnEmptyComponentInstructions (freeRegister)}
263
+
264
+ ${returnEmptyComponentInstructions (freeRegister)}
218
265
"""
219
- },
220
- ExternalLabel (" unfiltered" , getInstruction(insertHookIndex)),
221
- )
266
+ )
267
+ }
222
268
}
223
269
224
270
// endregion
0 commit comments