Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
import com.spotify.remoteconfig.internal.AccountAttribute;

import java.util.*;

Expand All @@ -14,25 +15,6 @@
@SuppressWarnings("unused")
public final class UnlockPremiumPatch {

private static final String SPOTIFY_MAIN_ACTIVITY_LEGACY = "com.spotify.music.MainActivity";

/**
* If the app target is 8.6.98.900.
*/
private static final boolean IS_SPOTIFY_LEGACY_APP_TARGET;

static {
boolean isLegacy;
try {
Class.forName(SPOTIFY_MAIN_ACTIVITY_LEGACY);
isLegacy = true;
} catch (ClassNotFoundException ex) {
isLegacy = false;
}

IS_SPOTIFY_LEGACY_APP_TARGET = isLegacy;
}

private static class OverrideAttribute {
/**
* Account attribute key.
Expand Down Expand Up @@ -66,7 +48,7 @@ private static class OverrideAttribute {
new OverrideAttribute("ads", FALSE),
// Works along on-demand, allows playing any song without restriction.
new OverrideAttribute("player-license", "premium"),
new OverrideAttribute("player-license-v2", "premium", !IS_SPOTIFY_LEGACY_APP_TARGET),
new OverrideAttribute("player-license-v2", "premium"),
// Disables shuffle being initially enabled when first playing a playlist.
new OverrideAttribute("shuffle", FALSE),
// Allows playing any song on-demand, without a shuffled order.
Expand All @@ -75,7 +57,7 @@ private static class OverrideAttribute {
new OverrideAttribute("streaming", TRUE),
// Allows adding songs to queue and removes the smart shuffle mode restriction,
// allowing to pick any of the other modes. Flag is not present in legacy app target.
new OverrideAttribute("pick-and-shuffle", FALSE, !IS_SPOTIFY_LEGACY_APP_TARGET),
new OverrideAttribute("pick-and-shuffle", FALSE),
// Disables shuffle-mode streaming-rule, which forces songs to be played shuffled
// and breaks the player when other patches are applied.
new OverrideAttribute("streaming-rules", ""),
Expand Down Expand Up @@ -131,10 +113,10 @@ private static class OverrideAttribute {
/**
* Injection point. Override account attributes.
*/
public static void overrideAttributes(Map<String, /*AccountAttribute*/ Object> attributes) {
public static void overrideAttributes(Map<String, AccountAttribute> attributes) {
try {
for (OverrideAttribute override : PREMIUM_OVERRIDES) {
Object attribute = attributes.get(override.key);
AccountAttribute attribute = attributes.get(override.key);

if (attribute == null) {
if (override.isExpected) {
Expand All @@ -145,11 +127,7 @@ public static void overrideAttributes(Map<String, /*AccountAttribute*/ Object> a

Object overrideValue = override.overrideValue;
Object originalValue;
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
originalValue = ((com.spotify.useraccount.v1.AccountAttribute) attribute).value_;
} else {
originalValue = ((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_;
}
originalValue = attribute.value_;

if (overrideValue.equals(originalValue)) {
continue;
Expand All @@ -158,11 +136,7 @@ public static void overrideAttributes(Map<String, /*AccountAttribute*/ Object> a
Logger.printInfo(() -> "Overriding account attribute " + override.key +
" from " + originalValue + " to " + overrideValue);

if (IS_SPOTIFY_LEGACY_APP_TARGET) {
((com.spotify.useraccount.v1.AccountAttribute) attribute).value_ = overrideValue;
} else {
((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_ = overrideValue;
}
attribute.value_ = overrideValue;
}
} catch (Exception ex) {
Logger.printException(() -> "overrideAttributes failure", ex);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
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.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import java.util.logging.Logger

private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
Expand All @@ -26,13 +24,6 @@ val hideCreateButtonPatch = bytecodePatch(
dependsOn(sharedExtensionPatch)

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
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.*
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element
Expand All @@ -19,12 +19,6 @@ private val customThemeBytecodePatch = bytecodePatch {
dependsOn(sharedExtensionPatch)

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

// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
package app.revanced.patches.spotify.lite.ondemand

import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch

@Deprecated("Patch no longer works and will be deleted soon")
@Suppress("unused")
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.",
) {
compatibleWith("com.spotify.lite")

execute {
// Spoof a premium account

onDemandFingerprint.method.addInstruction(
onDemandFingerprint.patternMatch!!.endIndex - 1,
"const/4 v0, 0x2",
)
}
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package app.revanced.patches.spotify.misc

import app.revanced.patcher.fingerprint
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.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
Expand All @@ -13,25 +12,13 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference

context(BytecodePatchContext)
internal val accountAttributeFingerprint get() = fingerprint {
custom { _, classDef ->
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/useraccount/v1/AccountAttribute;"
} else {
"Lcom/spotify/remoteconfig/internal/AccountAttribute;"
}
}
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
}

context(BytecodePatchContext)
internal val productStateProtoGetMapFingerprint get() = fingerprint {
returns("Ljava/util/Map;")
custom { _, classDef ->
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;"
} else {
"Lcom/spotify/remoteconfig/internal/ProductStateProto;"
}
}
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
}

internal val buildQueryParametersFingerprint = fingerprint {
Expand Down Expand Up @@ -90,14 +77,14 @@ internal val contextFromJsonFingerprint = fingerprint {
)
custom { method, classDef ->
method.name == "fromJson" &&
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
}
}

internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { method, classDef ->
method.name == "readPlayerOptionOverrides" &&
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
}
}

Expand All @@ -119,21 +106,21 @@ internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {

internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
custom { method, classDef ->
classDef.endsWith(className) && method.indexOfFirstInstruction {
classDef.type.endsWith(className) && method.indexOfFirstInstruction {
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
} >= 0
}
}

internal val homeSectionFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
}

internal val homeStructureGetSectionsFingerprint =
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")

internal val browseSectionFingerprint = fingerprint {
custom { _, classDef-> classDef.endsWith("browsita/v1/resolved/Section;") }
custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
}

internal val browseStructureGetSectionsFingerprint =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.*
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
Expand All @@ -23,7 +22,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import java.util.logging.Logger

internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;"

Expand Down Expand Up @@ -78,14 +76,6 @@ val unlockPremiumPatch = bytecodePatch(
}


if (IS_SPOTIFY_LEGACY_APP_TARGET) {
Logger.getLogger(this::class.java.name).warning(
"Patching a legacy Spotify version. Patch functionality may be limited."
)
return@execute
}


// Enable choosing a specific song/artist via Google Assistant.
contextFromJsonFingerprint.method.apply {
val insertIndex = contextFromJsonFingerprint.patternMatch!!.startIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ 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
Expand Down Expand Up @@ -57,16 +56,10 @@ val changeLyricsProviderPatch = bytecodePatch(
}

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;"
Expand All @@ -89,9 +82,11 @@ val changeLyricsProviderPatch = bytecodePatch(
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
Expand All @@ -118,6 +113,7 @@ val changeLyricsProviderPatch = bytecodePatch(
)
)
}

//endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal val shareCopyUrlFingerprint = fingerprint {
}
}

internal val shareCopyUrlLegacyFingerprint = fingerprint {
internal val oldShareCopyUrlFingerprint = fingerprint {
returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;")
strings("clipboard", "createNewSession failed")
Expand All @@ -38,7 +38,7 @@ internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
}
}

internal val formatAndroidShareSheetUrlLegacyFingerprint = fingerprint {
internal val oldFormatAndroidShareSheetUrlFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Ljava/lang/String;")
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")
Expand Down
Loading
Loading