@@ -4,21 +4,25 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
4
4
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
5
5
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
6
6
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
7
+ import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
7
8
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
8
- import app.revanced.patcher.fingerprint
9
+ import app.revanced.patcher.patch.PatchException
9
10
import app.revanced.patcher.patch.bytecodePatch
11
+ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
12
+ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
10
13
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
11
14
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
12
15
import app.revanced.util.getReference
13
16
import app.revanced.util.indexOfFirstInstructionOrThrow
14
17
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
15
- import com.android.tools.smali.dexlib2.AccessFlags
18
+ import app.revanced.util.toPublicAccessFlags
16
19
import com.android.tools.smali.dexlib2.Opcode
17
20
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
18
21
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
19
22
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
20
23
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
21
24
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
25
+ import com.android.tools.smali.dexlib2.iface.reference.TypeReference
22
26
import java.util.logging.Logger
23
27
24
28
private const val EXTENSION_CLASS_DESCRIPTOR = " Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;"
@@ -33,14 +37,18 @@ val unlockPremiumPatch = bytecodePatch(
33
37
dependsOn(sharedExtensionPatch)
34
38
35
39
execute {
36
- // Make _value accessible so that it can be overridden in the extension.
37
- accountAttributeFingerprint.classDef.fields.first { it.name == " value_" }.apply {
38
- // Add public flag and remove private.
39
- accessFlags = accessFlags.or (AccessFlags .PUBLIC .value).and (AccessFlags .PRIVATE .value.inv ())
40
+ fun MutableClass.publicizeField (fieldName : String ) {
41
+ fields.first { it.name == fieldName }.apply {
42
+ // Add public and remove private flag.
43
+ accessFlags = accessFlags.toPublicAccessFlags()
44
+ }
40
45
}
41
46
47
+ // Make _value accessible so that it can be overridden in the extension.
48
+ accountAttributeFingerprint.classDef.publicizeField(" value_" )
49
+
42
50
// Override the attributes map in the getter method.
43
- productStateProtoFingerprint .method.apply {
51
+ productStateProtoGetMapFingerprint .method.apply {
44
52
val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode .IGET_OBJECT )
45
53
val attributesMapRegister = getInstruction<TwoRegisterInstruction >(getAttributesMapIndex).registerA
46
54
@@ -53,12 +61,12 @@ val unlockPremiumPatch = bytecodePatch(
53
61
54
62
55
63
// Add the query parameter trackRows to show popular tracks in the artist page.
56
- buildQueryParametersFingerprint.apply {
57
- val addQueryParameterConditionIndex = method. indexOfFirstInstructionReversedOrThrow(
58
- stringMatches!! .first().index, Opcode .IF_EQZ
64
+ buildQueryParametersFingerprint.method. apply {
65
+ val addQueryParameterConditionIndex = indexOfFirstInstructionReversedOrThrow(
66
+ buildQueryParametersFingerprint. stringMatches!! .first().index, Opcode .IF_EQZ
59
67
)
60
68
61
- method. replaceInstruction(addQueryParameterConditionIndex, " nop" )
69
+ replaceInstruction(addQueryParameterConditionIndex, " nop" )
62
70
}
63
71
64
72
@@ -96,48 +104,39 @@ val unlockPremiumPatch = bytecodePatch(
96
104
val shufflingContextCallIndex = indexOfFirstInstructionOrThrow {
97
105
getReference<MethodReference >()?.name == " shufflingContext"
98
106
}
107
+ val boolRegister = getInstruction<FiveRegisterInstruction >(shufflingContextCallIndex).registerD
99
108
100
- val registerBool = getInstruction<FiveRegisterInstruction >(shufflingContextCallIndex).registerD
101
109
addInstruction(
102
110
shufflingContextCallIndex,
103
- " sget-object v$registerBool , Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
111
+ " sget-object v$boolRegister , Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
104
112
)
105
113
}
106
114
107
115
108
116
// Disable the "Spotify Premium" upsell experiment in context menus.
109
- contextMenuExperimentsFingerprint.apply {
110
- val moveIsEnabledIndex = method. indexOfFirstInstructionOrThrow(
111
- stringMatches!! .first().index, Opcode .MOVE_RESULT
117
+ contextMenuExperimentsFingerprint.method. apply {
118
+ val moveIsEnabledIndex = indexOfFirstInstructionOrThrow(
119
+ contextMenuExperimentsFingerprint. stringMatches!! .first().index, Opcode .MOVE_RESULT
112
120
)
113
- val isUpsellEnabledRegister = method. getInstruction<OneRegisterInstruction >(moveIsEnabledIndex).registerA
121
+ val isUpsellEnabledRegister = getInstruction<OneRegisterInstruction >(moveIsEnabledIndex).registerA
114
122
115
- method. replaceInstruction(moveIsEnabledIndex, " const/4 v$isUpsellEnabledRegister , 0" )
123
+ replaceInstruction(moveIsEnabledIndex, " const/4 v$isUpsellEnabledRegister , 0" )
116
124
}
117
125
118
126
119
- // Make featureTypeCase_ accessible so we can check the home section type in the extension.
120
- homeSectionFingerprint.classDef.fields.first { it.name == " featureTypeCase_" }.apply {
121
- // Add public flag and remove private.
122
- accessFlags = accessFlags.or (AccessFlags .PUBLIC .value).and (AccessFlags .PRIVATE .value.inv ())
123
- }
124
-
125
- val protobufListClassName = with (protobufListsFingerprint.originalMethod) {
127
+ val protobufListClassDef = with (protobufListsFingerprint.originalMethod) {
126
128
val emptyProtobufListGetIndex = indexOfFirstInstructionOrThrow(Opcode .SGET_OBJECT )
127
- getInstruction(emptyProtobufListGetIndex).getReference< FieldReference >() !! . definingClass
128
- }
129
+ // Find the protobuffer list class using the definingClass which contains the empty list static value.
130
+ val classType = getInstruction(emptyProtobufListGetIndex).getReference< FieldReference >() !! .definingClass
129
131
130
- val protobufListRemoveFingerprint = fingerprint {
131
- custom { method, classDef ->
132
- method.name == " remove" && classDef.type == protobufListClassName
133
- }
132
+ classes.find { it.type == classType } ? : throw PatchException (" Could not find protobuffer list class." )
134
133
}
135
134
136
135
// Need to allow mutation of the list so the home ads sections can be removed.
137
136
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
138
137
// Forcing that always on breaks unrelated code in strange ways.
139
138
// Instead, remove the method call that checks if the list is unmodifiable.
140
- protobufListRemoveFingerprint.method.apply {
139
+ protobufListRemoveFingerprint.match(protobufListClassDef). method.apply {
141
140
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
142
141
val reference = getReference<MethodReference >()
143
142
opcode == Opcode .INVOKE_VIRTUAL &&
@@ -148,8 +147,12 @@ val unlockPremiumPatch = bytecodePatch(
148
147
removeInstruction(invokeThrowUnmodifiableIndex)
149
148
}
150
149
150
+
151
+ // Make featureTypeCase_ accessible so we can check the home section type in the extension.
152
+ homeSectionFingerprint.classDef.publicizeField(" featureTypeCase_" )
153
+
151
154
// Remove ads sections from home.
152
- homeStructureFingerprint .method.apply {
155
+ homeStructureGetSectionsFingerprint .method.apply {
153
156
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode .IGET_OBJECT )
154
157
val sectionsRegister = getInstruction<TwoRegisterInstruction >(getSectionsIndex).registerA
155
158
@@ -159,5 +162,56 @@ val unlockPremiumPatch = bytecodePatch(
159
162
" $EXTENSION_CLASS_DESCRIPTOR ->removeHomeSections(Ljava/util/List;)V"
160
163
)
161
164
}
165
+
166
+
167
+ // Replace a fetch request that returns and maps Singles with their static onErrorReturn value.
168
+ fun MutableMethod.replaceFetchRequestSingleWithError (requestClassName : String ) {
169
+ // The index of where the request class is being instantiated.
170
+ val requestInstantiationIndex = indexOfFirstInstructionOrThrow {
171
+ getReference<TypeReference >()?.type?.endsWith(requestClassName) == true
172
+ }
173
+
174
+ // The index of where the onErrorReturn method is called with the error static value.
175
+ val onErrorReturnCallIndex = indexOfFirstInstructionOrThrow(requestInstantiationIndex) {
176
+ getReference<MethodReference >()?.name == " onErrorReturn"
177
+ }
178
+ val onErrorReturnCallInstruction = getInstruction<FiveRegisterInstruction >(onErrorReturnCallIndex)
179
+
180
+ // The error static value register.
181
+ val onErrorReturnValueRegister = onErrorReturnCallInstruction.registerD
182
+
183
+ // The index where the error static value starts being constructed.
184
+ // Because the Singles are mapped, the error static value starts being constructed right after the first
185
+ // move-result-object of the map call, before the onErrorReturn method call.
186
+ val onErrorReturnValueConstructionIndex =
187
+ indexOfFirstInstructionReversedOrThrow(onErrorReturnCallIndex, Opcode .MOVE_RESULT_OBJECT ) + 1
188
+
189
+ val singleClassName = onErrorReturnCallInstruction.getReference<MethodReference >()!! .definingClass
190
+ // The index where the request is firstly called, before its result is mapped to other values.
191
+ val requestCallIndex = indexOfFirstInstructionOrThrow(requestInstantiationIndex) {
192
+ getReference<MethodReference >()?.returnType == singleClassName
193
+ }
194
+
195
+ // Construct a new single with the error static value and return it.
196
+ addInstructions(
197
+ onErrorReturnCallIndex,
198
+ " invoke-static { v$onErrorReturnValueRegister }, " +
199
+ " $singleClassName ->just(Ljava/lang/Object;)$singleClassName \n " +
200
+ " move-result-object v$onErrorReturnValueRegister \n " +
201
+ " return-object v$onErrorReturnValueRegister "
202
+ )
203
+
204
+ // Remove every instruction from the request call to right before the error static value construction.
205
+ val removeCount = onErrorReturnValueConstructionIndex - requestCallIndex
206
+ removeInstructions(requestCallIndex, removeCount)
207
+ }
208
+
209
+ // Remove pendragon (pop up ads) requests and return the errors instead.
210
+ pendragonJsonFetchMessageRequestFingerprint.method.replaceFetchRequestSingleWithError(
211
+ PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME
212
+ )
213
+ pendragonProtoFetchMessageListRequestFingerprint.method.replaceFetchRequestSingleWithError(
214
+ PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME
215
+ )
162
216
}
163
217
}
0 commit comments