feat(Spotify): Remove support for old versions (#5404)

This commit is contained in:
Nuckyz
2025-07-11 12:37:59 -03:00
committed by GitHub
parent 536e64565c
commit c9cc3d5c41
11 changed files with 29 additions and 139 deletions

View File

@@ -1,8 +0,0 @@
package com.spotify.useraccount.v1;
/**
* Used for target 8.6.98.900. Class is still present in newer app targets.
*/
public class AccountAttribute {
public Object value_;
}

View File

@@ -6,12 +6,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import java.util.logging.Logger
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;" "Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
@@ -26,13 +24,6 @@ val hideCreateButtonPatch = bytecodePatch(
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
execute { execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
Logger.getLogger(this::class.java.name).warning(
"Create button does not exist in legacy app target. No changes applied."
)
return@execute
}
val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist. // Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) { val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {

View File

@@ -7,8 +7,8 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET import app.revanced.util.getReference
import app.revanced.util.* import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element import org.w3c.dom.Element
@@ -19,12 +19,6 @@ private val customThemeBytecodePatch = bytecodePatch {
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
execute { execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
// Bytecode changes are not needed for legacy app target.
// Player background color is changed with existing resource patch.
return@execute
}
val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef
// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors. // Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.

View File

@@ -1,23 +0,0 @@
package app.revanced.patches.spotify.lite.ondemand
import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint
internal val onDemandFingerprint = fingerprint(fuzzyPatternScanThreshold = 2) {
returns("L")
parameters()
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_EQZ,
Opcode.SGET_OBJECT,
Opcode.GOTO,
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IPUT,
Opcode.RETURN_OBJECT,
)
}

View File

@@ -1,21 +1,9 @@
package app.revanced.patches.spotify.lite.ondemand package app.revanced.patches.spotify.lite.ondemand
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch no longer works and will be deleted soon") @Deprecated("Patch no longer works and will be deleted soon")
@Suppress("unused") @Suppress("unused")
val onDemandPatch = bytecodePatch( val onDemandPatch = bytecodePatch(
description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.", description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.",
) { )
compatibleWith("com.spotify.lite")
execute {
// Spoof a premium account
onDemandFingerprint.method.addInstruction(
onDemandFingerprint.patternMatch!!.endIndex - 1,
"const/4 v0, 0x2",
)
}
}

View File

@@ -2,7 +2,6 @@ package app.revanced.patches.spotify.misc
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@@ -13,25 +12,13 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference
context(BytecodePatchContext) context(BytecodePatchContext)
internal val accountAttributeFingerprint get() = fingerprint { internal val accountAttributeFingerprint get() = fingerprint {
custom { _, classDef -> custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/useraccount/v1/AccountAttribute;"
} else {
"Lcom/spotify/remoteconfig/internal/AccountAttribute;"
}
}
} }
context(BytecodePatchContext) context(BytecodePatchContext)
internal val productStateProtoGetMapFingerprint get() = fingerprint { internal val productStateProtoGetMapFingerprint get() = fingerprint {
returns("Ljava/util/Map;") returns("Ljava/util/Map;")
custom { _, classDef -> custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;"
} else {
"Lcom/spotify/remoteconfig/internal/ProductStateProto;"
}
}
} }
internal val buildQueryParametersFingerprint = fingerprint { internal val buildQueryParametersFingerprint = fingerprint {
@@ -90,14 +77,14 @@ internal val contextFromJsonFingerprint = fingerprint {
) )
custom { method, classDef -> custom { method, classDef ->
method.name == "fromJson" && method.name == "fromJson" &&
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;") classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
} }
} }
internal val readPlayerOptionOverridesFingerprint = fingerprint { internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { method, classDef -> custom { method, classDef ->
method.name == "readPlayerOptionOverrides" && method.name == "readPlayerOptionOverrides" &&
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;") classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
} }
} }
@@ -119,21 +106,21 @@ internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
internal fun structureGetSectionsFingerprint(className: String) = fingerprint { internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
custom { method, classDef -> custom { method, classDef ->
classDef.endsWith(className) && method.indexOfFirstInstruction { classDef.type.endsWith(className) && method.indexOfFirstInstruction {
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_" opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
} >= 0 } >= 0
} }
} }
internal val homeSectionFingerprint = fingerprint { internal val homeSectionFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") } custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
} }
internal val homeStructureGetSectionsFingerprint = internal val homeStructureGetSectionsFingerprint =
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;") structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
internal val browseSectionFingerprint = fingerprint { internal val browseSectionFingerprint = fingerprint {
custom { _, classDef-> classDef.endsWith("browsita/v1/resolved/Section;") } custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
} }
internal val browseStructureGetSectionsFingerprint = internal val browseStructureGetSectionsFingerprint =

View File

@@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod 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.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
@@ -57,16 +56,10 @@ val changeLyricsProviderPatch = bytecodePatch(
} }
execute { 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 val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host. // region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) { val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow { val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;" getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
@@ -89,9 +82,11 @@ val changeLyricsProviderPatch = bytecodePatch(
httpClientBuilderFingerprint.classDef.methods.add(this) httpClientBuilderFingerprint.classDef.methods.add(this)
} }
} }
//endregion //endregion
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one. // region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply { getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow { val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>() == httpClientBuilderMethod getReference<MethodReference>() == httpClientBuilderMethod
@@ -118,6 +113,7 @@ val changeLyricsProviderPatch = bytecodePatch(
) )
) )
} }
//endregion //endregion
} }
} }

View File

@@ -14,7 +14,7 @@ internal val shareCopyUrlFingerprint = fingerprint {
} }
} }
internal val shareCopyUrlLegacyFingerprint = fingerprint { internal val oldShareCopyUrlFingerprint = fingerprint {
returns("Ljava/lang/Object;") returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;") parameters("Ljava/lang/Object;")
strings("clipboard", "createNewSession failed") strings("clipboard", "createNewSession failed")
@@ -38,7 +38,7 @@ internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
} }
} }
internal val formatAndroidShareSheetUrlLegacyFingerprint = fingerprint { internal val oldFormatAndroidShareSheetUrlFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC) accessFlags(AccessFlags.PUBLIC)
returns("Ljava/lang/String;") returns("Ljava/lang/String;")
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;") parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")

View File

@@ -1,11 +1,9 @@
package app.revanced.patches.spotify.misc.privacy package app.revanced.patches.spotify.misc.privacy
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@@ -28,10 +26,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" + val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
"sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;" "sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;"
val copyFingerprint = if (IS_SPOTIFY_LEGACY_APP_TARGET) { val copyFingerprint = if (shareCopyUrlFingerprint.originalMethodOrNull != null) {
shareCopyUrlLegacyFingerprint
} else {
shareCopyUrlFingerprint shareCopyUrlFingerprint
} else {
oldShareCopyUrlFingerprint
} }
copyFingerprint.method.apply { copyFingerprint.method.apply {
@@ -50,15 +48,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
} }
// Android native share sheet is used for all other quick share types (X, WhatsApp, etc). // Android native share sheet is used for all other quick share types (X, WhatsApp, etc).
val shareUrlParameter : String val shareUrlParameter: String
val shareSheetFingerprint : Fingerprint val shareSheetFingerprint = if (formatAndroidShareSheetUrlFingerprint.originalMethodOrNull != null) {
if (IS_SPOTIFY_LEGACY_APP_TARGET) { val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod
shareSheetFingerprint = formatAndroidShareSheetUrlLegacyFingerprint shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags.accessFlags)) {
shareUrlParameter = "p2"
} else {
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod.accessFlags
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags)) {
// In newer implementations the method is static, so p0 is not `this`. // In newer implementations the method is static, so p0 is not `this`.
"p1" "p1"
} else { } else {
@@ -66,6 +59,11 @@ val sanitizeSharingLinksPatch = bytecodePatch(
// For that reason, add one to the parameter register. // For that reason, add one to the parameter register.
"p2" "p2"
} }
formatAndroidShareSheetUrlFingerprint
} else {
shareUrlParameter = "p2"
oldFormatAndroidShareSheetUrlFingerprint
} }
shareSheetFingerprint.method.addInstructions( shareSheetFingerprint.method.addInstructions(

View File

@@ -1,9 +1,7 @@
package app.revanced.patches.spotify.misc.widgets package app.revanced.patches.spotify.misc.widgets
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
import java.util.logging.Logger
@Suppress("unused") @Suppress("unused")
val fixThirdPartyLaunchersWidgets = bytecodePatch( val fixThirdPartyLaunchersWidgets = bytecodePatch(
@@ -13,14 +11,6 @@ val fixThirdPartyLaunchersWidgets = bytecodePatch(
compatibleWith("com.spotify.music") compatibleWith("com.spotify.music")
execute { execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
// The permission check does not exist in legacy versions.
Logger.getLogger(this::class.java.name).warning(
"Legacy app target does not have any third party launcher restrictions. No changes applied."
)
return@execute
}
// Only system app launchers are granted the BIND_APPWIDGET permission. // Only system app launchers are granted the BIND_APPWIDGET permission.
// Override the method that checks for it to always return true, as this permission is not actually required // Override the method that checks for it to always return true, as this permission is not actually required
// for the widgets to work. // for the widgets to work.

View File

@@ -1,38 +1,15 @@
package app.revanced.patches.spotify.shared package app.revanced.patches.spotify.shared
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;" private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
/**
* Main activity of target 8.6.98.900.
*/
internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;"
internal val mainActivityOnCreateFingerprint = fingerprint { internal val mainActivityOnCreateFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V") returns("V")
parameters("Landroid/os/Bundle;") parameters("Landroid/os/Bundle;")
custom { method, classDef -> custom { method, classDef ->
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY method.name == "onCreate" && classDef.type == SPOTIFY_MAIN_ACTIVITY
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
} }
} }
private var isLegacyAppTarget: Boolean? = null
/**
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
* supports Spotify integration on Kenwood/Pioneer car stereos.
*/
context(BytecodePatchContext)
internal val IS_SPOTIFY_LEGACY_APP_TARGET
get(): Boolean {
if (isLegacyAppTarget == null) {
isLegacyAppTarget = mainActivityOnCreateFingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
}
return isLegacyAppTarget!!
}