Skip to content

feat(Spotify): Add limited support for version 8.6.98.900 (last version that supports Kenwood and Pioneer car stereos) #4750

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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

import com.spotify.remoteconfig.internal.AccountAttribute;
import com.spotify.home.evopage.homeapi.proto.Section;

import java.util.List;
Expand All @@ -15,6 +14,22 @@
@SuppressWarnings("unused")
public final class UnlockPremiumPatch {

/**
* If the app is target is 8.6.98.900.
*/
private static final boolean USING_LEGACY_APP_TARGET;
static {
boolean legacy;
try {
Class.forName("com.spotify.remoteconfig.internal.AccountAttribute");
legacy = false;
} catch (ClassNotFoundException ex) {
legacy = true;
}

USING_LEGACY_APP_TARGET = legacy;
}

private static class OverrideAttribute {
/**
* Account attribute key.
Expand Down Expand Up @@ -55,8 +70,8 @@ private static class OverrideAttribute {
// Make sure playing songs is not disabled remotely and playlists show up.
new OverrideAttribute("streaming", TRUE),
// Allows adding songs to queue and removes the smart shuffle mode restriction,
// allowing to pick any of the other modes.
new OverrideAttribute("pick-and-shuffle", FALSE),
// allowing to pick any of the other modes. Flag is not present in legacy app target.
new OverrideAttribute("pick-and-shuffle", FALSE, !USING_LEGACY_APP_TARGET),
// 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 All @@ -78,7 +93,7 @@ private static class OverrideAttribute {
/**
* Override attributes injection point.
*/
public static void overrideAttribute(Map<String, AccountAttribute> attributes) {
public static void overrideAttribute(Map<String, /*AccountAttribute*/ Object> attributes) {
try {
for (var override : OVERRIDES) {
var attribute = attributes.get(override.key);
Expand All @@ -87,7 +102,12 @@ public static void overrideAttribute(Map<String, AccountAttribute> attributes) {
Logger.printException(() -> "'" + override.key + "' expected but not found");
}
} else {
attribute.value_ = override.overrideValue;
Object overrideValue = override.overrideValue;
if (USING_LEGACY_APP_TARGET) {
((com.spotify.useraccount.v1.AccountAttribute) attribute).value_ = overrideValue;
} else {
((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_ = overrideValue;
}
}
}
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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_;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,32 @@ import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode

internal const val SPOTIFY_ACCOUNT_ATTRIBUTE = "Lcom/spotify/remoteconfig/internal/AccountAttribute;"

/**
* Version 8.6.98.900.
*/
private const val SPOTIFY_ACCOUNT_ATTRIBUTE_LEGACY = "Lcom/spotify/useraccount/v1/AccountAttribute;"

internal val accountAttributeFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("internal/AccountAttribute;") }
custom { _, classDef ->
classDef.type == if (SPOTIFY_LEGACY_APP_TARGET) {
SPOTIFY_ACCOUNT_ATTRIBUTE_LEGACY
} else {
SPOTIFY_ACCOUNT_ATTRIBUTE
}
}
}

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

internal val buildQueryParametersFingerprint = fingerprint {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
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 java.util.logging.Logger

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

/**
* If patching 8.6.98.900.
*/
internal var SPOTIFY_LEGACY_APP_TARGET = false

@Suppress("unused")
val unlockPremiumPatch = bytecodePatch(
name = "Unlock Spotify Premium",
Expand All @@ -27,6 +33,9 @@ val unlockPremiumPatch = bytecodePatch(
dependsOn(sharedExtensionPatch)

execute {
// Check if patching 8.6.98.900.
SPOTIFY_LEGACY_APP_TARGET = (classes.find { it.type == SPOTIFY_ACCOUNT_ATTRIBUTE } == null)

// Make _value accessible so that it can be overridden in the extension.
accountAttributeFingerprint.classDef.fields.first { it.name == "value_" }.apply {
// Add public flag and remove private.
Expand All @@ -53,6 +62,12 @@ val unlockPremiumPatch = bytecodePatch(
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
}

if (SPOTIFY_LEGACY_APP_TARGET) {
return@execute Logger.getLogger(this::class.java.name).warning(
"Using a legacy app target and patch functionality may be limited."
)
}

// Disable the "Spotify Premium" upsell experiment in context menus.
with(contextMenuExperimentsFingerprint) {
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import app.revanced.patches.shared.misc.extension.extensionHook

internal val spotifyMainActivityOnCreate = extensionHook {
custom { method, classDef ->
classDef.type == "Lcom/spotify/music/SpotifyMainActivity;" &&
method.name == "onCreate"
method.name == "onCreate" && (classDef.type == "Lcom/spotify/music/SpotifyMainActivity;"
|| classDef.type == "Lcom/spotify/music/MainActivity;") // target 8.6.98.900
}
}