Skip to content

Commit 7bbaca7

Browse files
authored
feat(Spotify): Add Change lyrics provider patch (#4937)
1 parent 246f3ef commit 7bbaca7

File tree

6 files changed

+158
-4
lines changed

6 files changed

+158
-4
lines changed

extensions/spotify/src/main/java/app/revanced/extension/spotify/shared/ComponentFilters.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package app.revanced.extension.spotify.shared;
22

3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
35
import app.revanced.extension.shared.Logger;
46
import app.revanced.extension.shared.Utils;
57

68
public final class ComponentFilters {
79

810
public interface ComponentFilter {
11+
@NonNull
912
String getFilterValue();
1013
String getFilterRepresentation();
1114
default boolean filterUnavailable() {
@@ -20,7 +23,8 @@ public static final class ResourceIdComponentFilter implements ComponentFilter {
2023
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
2124
// 0 is returned when a resource has not been found.
2225
private int resourceId = -1;
23-
private String stringfiedResourceId = null;
26+
@Nullable
27+
private String stringfiedResourceId;
2428

2529
public ResourceIdComponentFilter(String resourceName, String resourceType) {
2630
this.resourceName = resourceName;
@@ -34,6 +38,7 @@ public int getResourceId() {
3438
return resourceId;
3539
}
3640

41+
@NonNull
3742
@Override
3843
public String getFilterValue() {
3944
if (stringfiedResourceId == null) {
@@ -66,6 +71,7 @@ public StringComponentFilter(String string) {
6671
this.string = string;
6772
}
6873

74+
@NonNull
6975
@Override
7076
public String getFilterValue() {
7177
return string;

patches/api/patches.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,10 @@ public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginP
921921
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
922922
}
923923

924+
public final class app/revanced/patches/spotify/misc/lyrics/ChangeLyricsProviderPatchKt {
925+
public static final fun getChangeLyricsProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
926+
}
927+
924928
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
925929
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
926930
}

patches/src/main/kotlin/app/revanced/patches/spotify/layout/hide/createbutton/HideCreateButtonPatch.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ val hideCreateButtonPatch = bytecodePatch(
9494
"""
9595
invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor
9696
move-result v0
97-
97+
9898
# If this navigation bar item is not the Create button, jump to the normal method logic.
9999
if-eqz v0, :normal-method-logic
100-
100+
101101
# Return null early because this method return value is a BottomNavigationItemView.
102102
const/4 v0, 0
103103
return-object v0

patches/src/main/kotlin/app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginPatch.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ val fixFacebookLoginPatch = bytecodePatch(
1919
// signature checks.
2020

2121
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef
22-
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
22+
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
2323
// make the login fallback to a web browser window.
2424
katanaProxyLoginMethodTryAuthorizeFingerprint
2525
.match(katanaProxyLoginMethodHandlerClass)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package app.revanced.patches.spotify.misc.lyrics
2+
3+
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
4+
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
5+
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
6+
import app.revanced.patcher.patch.bytecodePatch
7+
import app.revanced.patcher.patch.stringOption
8+
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
9+
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
10+
import app.revanced.util.getReference
11+
import app.revanced.util.indexOfFirstInstructionOrThrow
12+
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
13+
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
14+
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
15+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
16+
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
17+
import java.net.InetAddress
18+
import java.net.URI
19+
import java.net.URISyntaxException
20+
import java.net.UnknownHostException
21+
import java.util.logging.Logger
22+
23+
@Suppress("unused")
24+
val changeLyricsProviderPatch = bytecodePatch(
25+
name = "Change lyrics provider",
26+
description = "Changes the lyrics provider to a custom one.",
27+
use = false,
28+
) {
29+
compatibleWith("com.spotify.music")
30+
31+
val lyricsProviderHost by stringOption(
32+
key = "lyricsProviderHost",
33+
default = "lyrics.natanchiodi.fr",
34+
title = "Lyrics provider host",
35+
description = "The domain name or IP address of a custom lyrics provider.",
36+
required = false,
37+
) {
38+
// Fix bad data if the user enters a URL (https://whatever.com/path).
39+
val host = try {
40+
URI(it!!).host ?: it
41+
} catch (e: URISyntaxException) {
42+
return@stringOption false
43+
}
44+
45+
// Do a courtesy check if the host can be resolved.
46+
// If it does not resolve, then print a warning but use the host anyway.
47+
// Unresolvable hosts should not be rejected, since the patching environment
48+
// may not allow network connections or the network may be down.
49+
try {
50+
InetAddress.getByName(host)
51+
} catch (e: UnknownHostException) {
52+
Logger.getLogger(this::class.java.name).warning(
53+
"Host \"$host\" did not resolve to any domain."
54+
)
55+
}
56+
true
57+
}
58+
59+
execute {
60+
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
61+
Logger.getLogger(this::class.java.name).severe(
62+
"Change lyrics provider patch is not supported for this target version."
63+
)
64+
return@execute
65+
}
66+
67+
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
68+
69+
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
70+
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
71+
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
72+
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
73+
}
74+
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
75+
val reference = getReference<MethodReference>()
76+
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
77+
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
78+
}
79+
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
80+
81+
MutableMethod(this).apply {
82+
name = "rv_getCustomLyricsProviderHttpClient"
83+
addInstruction(
84+
setUrlBuilderHostIndex,
85+
"const-string v$hostRegister, \"$lyricsProviderHost\""
86+
)
87+
88+
// Add the patched method to the class.
89+
httpClientBuilderFingerprint.classDef.methods.add(this)
90+
}
91+
}
92+
//endregion
93+
94+
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
95+
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
96+
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
97+
getReference<MethodReference>() == httpClientBuilderMethod
98+
}
99+
val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)
100+
101+
// Replace the original method call with a call to our patched method.
102+
replaceInstruction(
103+
getLyricsHttpClientIndex,
104+
BuilderInstruction35c(
105+
getLyricsHttpClientInstruction.opcode,
106+
getLyricsHttpClientInstruction.registerCount,
107+
getLyricsHttpClientInstruction.registerC,
108+
getLyricsHttpClientInstruction.registerD,
109+
getLyricsHttpClientInstruction.registerE,
110+
getLyricsHttpClientInstruction.registerF,
111+
getLyricsHttpClientInstruction.registerG,
112+
ImmutableMethodReference(
113+
patchedHttpClientBuilderMethod.definingClass,
114+
patchedHttpClientBuilderMethod.name, // Only difference from the original method.
115+
patchedHttpClientBuilderMethod.parameters,
116+
patchedHttpClientBuilderMethod.returnType
117+
)
118+
)
119+
)
120+
}
121+
//endregion
122+
}
123+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package app.revanced.patches.spotify.misc.lyrics
2+
3+
import app.revanced.patcher.fingerprint
4+
import app.revanced.util.getReference
5+
import app.revanced.util.indexOfFirstInstruction
6+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
7+
8+
internal val httpClientBuilderFingerprint = fingerprint {
9+
strings("client == null", "scheduler == null")
10+
}
11+
12+
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) =
13+
fingerprint {
14+
returns(httpClientBuilderMethodReference.returnType)
15+
parameters()
16+
custom { method, _ ->
17+
method.indexOfFirstInstruction {
18+
getReference<MethodReference>() == httpClientBuilderMethodReference
19+
} >= 0
20+
}
21+
}

0 commit comments

Comments
 (0)