feat(Spotify): Add Change lyrics provider patch (#4937)

This commit is contained in:
brosssh
2025-06-11 10:25:58 +02:00
committed by GitHub
parent bdc6fad974
commit 8736b6a80b
6 changed files with 158 additions and 4 deletions

View File

@ -1,11 +1,14 @@
package app.revanced.extension.spotify.shared; package app.revanced.extension.spotify.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
public final class ComponentFilters { public final class ComponentFilters {
public interface ComponentFilter { public interface ComponentFilter {
@NonNull
String getFilterValue(); String getFilterValue();
String getFilterRepresentation(); String getFilterRepresentation();
default boolean filterUnavailable() { default boolean filterUnavailable() {
@ -20,7 +23,8 @@ public final class ComponentFilters {
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded. // Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
// 0 is returned when a resource has not been found. // 0 is returned when a resource has not been found.
private int resourceId = -1; private int resourceId = -1;
private String stringfiedResourceId = null; @Nullable
private String stringfiedResourceId;
public ResourceIdComponentFilter(String resourceName, String resourceType) { public ResourceIdComponentFilter(String resourceName, String resourceType) {
this.resourceName = resourceName; this.resourceName = resourceName;
@ -34,6 +38,7 @@ public final class ComponentFilters {
return resourceId; return resourceId;
} }
@NonNull
@Override @Override
public String getFilterValue() { public String getFilterValue() {
if (stringfiedResourceId == null) { if (stringfiedResourceId == null) {
@ -66,6 +71,7 @@ public final class ComponentFilters {
this.string = string; this.string = string;
} }
@NonNull
@Override @Override
public String getFilterValue() { public String getFilterValue() {
return string; return string;

View File

@ -921,6 +921,10 @@ public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginP
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/misc/lyrics/ChangeLyricsProviderPatchKt {
public static final fun getChangeLyricsProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt { public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }

View File

@ -0,0 +1,123 @@
package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import java.net.InetAddress
import java.net.URI
import java.net.URISyntaxException
import java.net.UnknownHostException
import java.util.logging.Logger
@Suppress("unused")
val changeLyricsProviderPatch = bytecodePatch(
name = "Change lyrics provider",
description = "Changes the lyrics provider to a custom one.",
use = false,
) {
compatibleWith("com.spotify.music")
val lyricsProviderHost by stringOption(
key = "lyricsProviderHost",
default = "lyrics.natanchiodi.fr",
title = "Lyrics provider host",
description = "The domain name or IP address of a custom lyrics provider.",
required = false,
) {
// Fix bad data if the user enters a URL (https://whatever.com/path).
val host = try {
URI(it!!).host ?: it
} catch (e: URISyntaxException) {
return@stringOption false
}
// Do a courtesy check if the host can be resolved.
// If it does not resolve, then print a warning but use the host anyway.
// Unresolvable hosts should not be rejected, since the patching environment
// may not allow network connections or the network may be down.
try {
InetAddress.getByName(host)
} catch (e: UnknownHostException) {
Logger.getLogger(this::class.java.name).warning(
"Host \"$host\" did not resolve to any domain."
)
}
true
}
execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
Logger.getLogger(this::class.java.name).severe(
"Change lyrics provider patch is not supported for this target version."
)
return@execute
}
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
}
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
val reference = getReference<MethodReference>()
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
}
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
MutableMethod(this).apply {
name = "rv_getCustomLyricsProviderHttpClient"
addInstruction(
setUrlBuilderHostIndex,
"const-string v$hostRegister, \"$lyricsProviderHost\""
)
// Add the patched method to the class.
httpClientBuilderFingerprint.classDef.methods.add(this)
}
}
//endregion
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>() == httpClientBuilderMethod
}
val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)
// Replace the original method call with a call to our patched method.
replaceInstruction(
getLyricsHttpClientIndex,
BuilderInstruction35c(
getLyricsHttpClientInstruction.opcode,
getLyricsHttpClientInstruction.registerCount,
getLyricsHttpClientInstruction.registerC,
getLyricsHttpClientInstruction.registerD,
getLyricsHttpClientInstruction.registerE,
getLyricsHttpClientInstruction.registerF,
getLyricsHttpClientInstruction.registerG,
ImmutableMethodReference(
patchedHttpClientBuilderMethod.definingClass,
patchedHttpClientBuilderMethod.name, // Only difference from the original method.
patchedHttpClientBuilderMethod.parameters,
patchedHttpClientBuilderMethod.returnType
)
)
)
}
//endregion
}
}

View File

@ -0,0 +1,21 @@
package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val httpClientBuilderFingerprint = fingerprint {
strings("client == null", "scheduler == null")
}
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) =
fingerprint {
returns(httpClientBuilderMethodReference.returnType)
parameters()
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>() == httpClientBuilderMethodReference
} >= 0
}
}